Skip to content

Commit 7267dc5

Browse files
committed
preserve typed input when validator invalidates controlled dates
1 parent ff72a37 commit 7267dc5

3 files changed

Lines changed: 111 additions & 3 deletions

File tree

packages/x-date-pickers/src/DateField/tests/editing.DateField.test.tsx

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import * as React from 'react';
12
import { spy } from 'sinon';
23
import { DateField } from '@mui/x-date-pickers/DateField';
34
import { act, fireEvent, waitFor } from '@mui/internal-test-utils';
@@ -63,6 +64,50 @@ describe('<DateField /> - Editing', () => {
6364
expectFieldValue(view.getSectionsContainer(), 'DD MMMM');
6465
});
6566
});
67+
68+
it('should keep the entered sections when the controlled value ignores a validator-invalid date', async () => {
69+
const view = renderWithProps(
70+
{
71+
minDate: adapter.date('2022-01-01'),
72+
maxDate: adapter.date('2022-12-31'),
73+
},
74+
{
75+
hook: function useControlledInvalidValueProps() {
76+
const [value, setValue] = React.useState(null);
77+
78+
return {
79+
value,
80+
onChange: (newValue, context) => {
81+
if (context.validationError == null) {
82+
setValue(newValue);
83+
}
84+
},
85+
};
86+
},
87+
},
88+
);
89+
90+
await view.selectSectionAsync('month');
91+
await view.user.keyboard('04');
92+
await view.selectSectionAsync('day');
93+
await view.user.keyboard('17');
94+
expectFieldValue(view.getSectionsContainer(), '04/17/YYYY');
95+
96+
await view.selectSectionAsync('year');
97+
await view.user.keyboard('2023');
98+
99+
await act(async () => {
100+
await new Promise((resolve) => {
101+
setTimeout(resolve, 0);
102+
});
103+
});
104+
105+
expectFieldValue(view.getSectionsContainer(), '04/17/2023');
106+
107+
await view.selectSectionAsync('year');
108+
await view.user.keyboard('2022');
109+
expectFieldValue(view.getSectionsContainer(), '04/17/2022');
110+
});
66111
},
67112
);
68113

packages/x-date-pickers/src/DesktopDatePicker/tests/field.DesktopDatePicker.test.tsx

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import * as React from 'react';
2+
import { act } from '@mui/internal-test-utils';
13
import { DesktopDatePicker, DesktopDatePickerProps } from '@mui/x-date-pickers/DesktopDatePicker';
24
import {
35
createPickerRenderer,
@@ -73,4 +75,55 @@ describe('<DesktopDatePicker /> - Field', () => {
7375
expectFieldValue(view.getSectionsContainer(), 'MMMM 2022');
7476
});
7577
});
78+
79+
describeAdapters(
80+
'Controlled invalid value',
81+
DesktopDatePicker,
82+
({ adapter, renderWithProps }) => {
83+
it('should keep the entered sections when the controlled value ignores a validator-invalid date', async () => {
84+
const view = renderWithProps(
85+
{
86+
minDate: adapter.date('2022-01-01'),
87+
maxDate: adapter.date('2022-12-31'),
88+
},
89+
{
90+
componentFamily: 'picker',
91+
hook: function useControlledInvalidValueProps() {
92+
const [value, setValue] = React.useState(null);
93+
94+
return {
95+
value,
96+
onChange: (newValue, context) => {
97+
if (context.validationError == null) {
98+
setValue(newValue);
99+
}
100+
},
101+
};
102+
},
103+
},
104+
);
105+
106+
await view.selectSectionAsync('month');
107+
await view.user.keyboard('04');
108+
await view.selectSectionAsync('day');
109+
await view.user.keyboard('17');
110+
expectFieldValue(view.getSectionsContainer(), '04/17/YYYY');
111+
112+
await view.selectSectionAsync('year');
113+
await view.user.keyboard('2023');
114+
115+
await act(async () => {
116+
await new Promise((resolve) => {
117+
setTimeout(resolve, 0);
118+
});
119+
});
120+
121+
expectFieldValue(view.getSectionsContainer(), '04/17/2023');
122+
123+
await view.selectSectionAsync('year');
124+
await view.user.keyboard('2022');
125+
expectFieldValue(view.getSectionsContainer(), '04/17/2022');
126+
});
127+
},
128+
);
76129
});

packages/x-date-pickers/src/internals/hooks/useField/useFieldState.ts

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ export const useFieldState = <
9191
valueRef.current = value;
9292
}, [value]);
9393

94-
const { hasValidationError } = useValidation({
94+
const { hasValidationError, getValidationErrorForNewValue } = useValidation({
9595
props: internalPropsWithDefaults,
9696
validator,
9797
timezone,
@@ -350,8 +350,18 @@ export const useFieldState = <
350350
fieldValueManager.getDateFromSection(state.referenceValue as any, section)!,
351351
true,
352352
);
353+
const newValue = fieldValueManager.updateDateInValue(value, section, mergedDate);
354+
const hasValidationErrorOnNewValue = validator.valueManager.hasError(
355+
getValidationErrorForNewValue(newValue),
356+
);
353357

354-
if (activeDate == null) {
358+
if (hasValidationErrorOnNewValue) {
359+
setState((prevState) => ({
360+
...prevState,
361+
sections: newSections,
362+
tempValueStrAndroid: null,
363+
}));
364+
} else if (activeDate == null) {
355365
cleanActiveDateSectionsIfValueNullTimeout.start(0, () => {
356366
if (valueRef.current === value) {
357367
setState((prevState) => ({
@@ -363,7 +373,7 @@ export const useFieldState = <
363373
});
364374
}
365375

366-
return publishValue(fieldValueManager.updateDateInValue(value, section, mergedDate));
376+
return publishValue(newValue);
367377
}
368378

369379
/**

0 commit comments

Comments
 (0)