Steps to reproduce
- Mount a controlled
DatePicker with value={null} and an action bar that includes the clear action
- Update the controlled
value prop externally (e.g. via Redux or parent state change — not through the picker UI)
- Open the picker and click the Clear action bar button
Expected: onAccept(null) fires, allowing the consumer to react to the clear.
Actual: onAccept is not called. Only onChange(null) fires (which many consumers treat as an intermediate/uncommitted change).
Why it happens
In useValueAndOpenStates.ts, when the controlled value prop changes externally, lastCommittedValue is not updated:
// useValueAndOpenStates.ts lines ~161-168
if (value !== state.lastExternalValue) {
setState(prevState => ({
...prevState,
lastExternalValue: value,
clockShallowValue: undefined,
hasBeenModifiedSinceMount: true,
// ⚠️ lastCommittedValue is NOT updated here
}));
}
Then when Clear is clicked, clearValue() → setValue(emptyValue, { source: 'view' }) → the accept guard compares:
shouldFireOnAccept = changeImportance === 'accept'
&& !valueManager.areValuesEqual(adapter, newValue, state.lastCommittedValue);
// null null (stale from mount!)
// → areValuesEqual(null, null) → true → shouldFireOnAccept = false → onAccept skipped
The same issue affects any action that compares against lastCommittedValue — Clear, Accept (if re-accepting the same externally-set value), and it also means Cancel would restore to the wrong value.
Contrast with working case
If the user sets the date through the picker UI (selecting from the calendar), onAccept fires and updates lastCommittedValue. Then clicking Clear correctly sees null !== date → onAccept(null) fires.
Minimal reproduction
import React, { useState, useEffect } from 'react';
import { DatePicker } from '@mui/x-date-pickers';
import { LocalizationProvider } from '@mui/x-date-pickers';
import { AdapterDateFns } from '@mui/x-date-pickers/AdapterDateFns';
import { parseISO } from 'date-fns';
export default function App() {
return (
<LocalizationProvider dateAdapter={AdapterDateFns}>
<BugRepro />
</LocalizationProvider>
);
}
function BugRepro() {
const [externalValue, setExternalValue] = useState(null);
return (
<div style={{ padding: 24, fontFamily: 'monospace' }}>
<h3>onAccept not called after external value change</h3>
<button onClick={() => setExternalValue('2025-06-15')}>
1. Set value externally (simulates Redux update)
</button>
<button onClick={() => setExternalValue(null)}>
Reset
</button>
<p>External value: <strong>{String(externalValue)}</strong></p>
<p>2. Now open the picker and click <strong>Clear</strong>. Check the console.</p>
<ControlledPicker
rawValue={externalValue}
onDateChange={(val) => {
console.log('onDateChange (from onAccept):', val);
setExternalValue(val);
}}
/>
</div>
);
}
function ControlledPicker({ rawValue, onDateChange }) {
const [value, setValue] = useState(rawValue ? parseISO(rawValue) : null);
useEffect(() => {
setValue(rawValue ? parseISO(rawValue) : null);
}, [rawValue]);
return (
<DatePicker
label="Test"
value={value}
onChange={(date) => {
console.log('onChange:', date);
setValue(date);
}}
onAccept={(date) => {
console.log('✅ onAccept:', date);
onDateChange(date ? date.toISOString().slice(0, 10) : null);
}}
slotProps={{
actionBar: { actions: ['clear', 'cancel', 'accept'] },
textField: { size: 'small' },
}}
format="yyyy-MM-dd"
/>
);
}
Steps with the repro:
- Click "Set value externally" — the picker displays
2025-06-15
- Open the picker → click Clear
- Observe the console:
onChange: null fires, but onAccept never fires
Now compare:
- Click Reset to go back to null
- Open the picker → select any date from the calendar → click Accept (this fires
onAccept and updates lastCommittedValue)
- Open the picker again → click Clear
- This time
onAccept(null) does fire
Proposed fix
Update the external value sync block in useValueAndOpenStates.ts to also update lastCommittedValue:
if (value !== state.lastExternalValue) {
setState(prevState => ({
...prevState,
lastExternalValue: value,
lastCommittedValue: value, // ← sync committed value with external changes
clockShallowValue: undefined,
hasBeenModifiedSinceMount: true,
}));
}
System info
| Tech |
Version |
| @mui/x-date-pickers |
8.27.0 |
| @mui/material |
7.3.8 |
| React |
18.3.1 |
| Browser |
Chrome 135 |
Search keywords: onAccept not called, clear button, controlled DatePicker, external value change, lastCommittedValue
Steps to reproduce
DatePickerwithvalue={null}and an action bar that includes theclearactionvalueprop externally (e.g. via Redux or parent state change — not through the picker UI)Expected:
onAccept(null)fires, allowing the consumer to react to the clear.Actual:
onAcceptis not called. OnlyonChange(null)fires (which many consumers treat as an intermediate/uncommitted change).Why it happens
In
useValueAndOpenStates.ts, when the controlledvalueprop changes externally,lastCommittedValueis not updated:Then when Clear is clicked,
clearValue()→setValue(emptyValue, { source: 'view' })→ the accept guard compares:The same issue affects any action that compares against
lastCommittedValue— Clear, Accept (if re-accepting the same externally-set value), and it also means Cancel would restore to the wrong value.Contrast with working case
If the user sets the date through the picker UI (selecting from the calendar),
onAcceptfires and updateslastCommittedValue. Then clicking Clear correctly seesnull !== date→onAccept(null)fires.Minimal reproduction
Steps with the repro:
2025-06-15onChange: nullfires, butonAcceptnever firesNow compare:
onAcceptand updateslastCommittedValue)onAccept(null)does fireProposed fix
Update the external value sync block in
useValueAndOpenStates.tsto also updatelastCommittedValue:System info
Search keywords: onAccept not called, clear button, controlled DatePicker, external value change, lastCommittedValue