Skip to content

Commit 5cc73a0

Browse files
author
React-Admin CI
committed
Simplify code, test reset
1 parent 414c4ad commit 5cc73a0

File tree

5 files changed

+91
-43
lines changed

5 files changed

+91
-43
lines changed

packages/ra-ui-materialui/src/input/DateInput.spec.tsx

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,25 @@ describe('<DateInput />', () => {
265265
await screen.findByText('"2021-10-20" (string)');
266266
});
267267

268+
it('should change its value when the form value is reset', async () => {
269+
render(
270+
<ExternalChanges
271+
simpleFormProps={{
272+
defaultValues: { publishedAt: '2021-09-11' },
273+
}}
274+
/>
275+
);
276+
await screen.findByText('"2021-09-11" (string)');
277+
const input = screen.getByLabelText('Published at') as HTMLInputElement;
278+
fireEvent.change(input, {
279+
target: { value: '2021-10-30' },
280+
});
281+
fireEvent.blur(input);
282+
await screen.findByText('"2021-10-30" (string)');
283+
fireEvent.click(screen.getByText('Reset'));
284+
await screen.findByText('"2021-09-11" (string)');
285+
});
286+
268287
describe('error message', () => {
269288
it('should not be displayed if field is pristine', () => {
270289
render(<Basic dateInputProps={{ validate: required() }} />);

packages/ra-ui-materialui/src/input/DateInput.stories.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,14 +104,16 @@ export const Parse = ({ simpleFormProps }) => (
104104
);
105105

106106
export const ExternalChanges = ({
107+
dateInputProps = {},
107108
simpleFormProps = {
108109
defaultValues: { publishedAt: '2021-09-11' },
109110
},
110111
}: {
112+
dateInputProps?: Partial<DateInputProps>;
111113
simpleFormProps?: Omit<SimpleFormProps, 'children'>;
112114
}) => (
113115
<Wrapper simpleFormProps={simpleFormProps}>
114-
<DateInput source="publishedAt" />
116+
<DateInput source="publishedAt" {...dateInputProps} />
115117
<DateHelper source="publishedAt" value="2021-10-20" />
116118
</Wrapper>
117119
);

packages/ra-ui-materialui/src/input/DateInput.tsx

Lines changed: 25 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -67,24 +67,33 @@ export const DateInput = ({
6767
format,
6868
...rest,
6969
});
70-
const [renderCount, setRenderCount] = React.useState(1);
71-
const valueChangedFromInput = React.useRef(false);
7270
const localInputRef = React.useRef<HTMLInputElement>();
71+
// DateInput is not a really controlled input to ensure users can start entering a date, go to another input and come back to complete it.
72+
// This ref stores the value that is passed to the input defaultValue prop to solve this issue.
7373
const initialDefaultValueRef = React.useRef(field.value);
74-
const currentValueRef = React.useRef(field.value);
74+
// As the defaultValue prop won't trigger a remount of the HTML input, we will force it by changing the key.
75+
const [inputKey, setInputKey] = React.useState(1);
76+
// This ref let us track that the last change of the form state value was made by the input itself
77+
const wasLastChangedByInput = React.useRef(false);
7578

76-
// update the react-hook-form value if the field value changes
79+
// This effect ensures we stays in sync with the react-hook-form state when the value changes from outside the input
80+
// for instance by using react-hook-form reset or setValue methods.
7781
React.useEffect(() => {
78-
if (
79-
currentValueRef.current !== field.value &&
80-
!valueChangedFromInput.current
81-
) {
82-
setRenderCount(r => r + 1);
83-
initialDefaultValueRef.current = field.value;
84-
currentValueRef.current = field.value;
82+
// Ignore react-hook-form state changes if it came from the input itself
83+
if (wasLastChangedByInput.current) {
84+
// Resets the flag to ensure futures changes are handled
85+
wasLastChangedByInput.current = false;
86+
return;
8587
}
86-
valueChangedFromInput.current = false;
87-
}, [setRenderCount, field]);
88+
89+
// The value has changed from outside the input, we update the input value
90+
initialDefaultValueRef.current = field.value;
91+
// Trigger a remount of the HTML input
92+
setInputKey(r => r + 1);
93+
// Resets the flag to ensure futures changes are handled
94+
wasLastChangedByInput.current = false;
95+
}, [setInputKey, field.value]);
96+
8897
const { onBlur: onBlurFromField } = field;
8998
const hasFocus = React.useRef(false);
9099

@@ -113,8 +122,8 @@ export const DateInput = ({
113122
// The input reset is handled in the onBlur event handler
114123
if (newValue !== '' && newValue != null && isNewValueValid) {
115124
field.onChange(newValue);
116-
valueChangedFromInput.current = true;
117-
currentValueRef.current = newValue;
125+
// Track the fact that the next react-hook-form state change was triggered by the input itself
126+
wasLastChangedByInput.current = true;
118127
}
119128
}
120129
);
@@ -163,7 +172,7 @@ export const DateInput = ({
163172
name={name}
164173
inputRef={inputRef}
165174
defaultValue={format(initialDefaultValueRef.current)}
166-
key={renderCount}
175+
key={inputKey}
167176
type="date"
168177
onChange={handleChange}
169178
onFocus={handleFocus}

packages/ra-ui-materialui/src/input/DateTimeInput.spec.tsx

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,25 @@ describe('<DateTimeInput />', () => {
217217
await screen.findByText('"2021-10-20 10:00:00" (string)');
218218
});
219219

220+
it('should change its value when the form value is reset', async () => {
221+
render(
222+
<ExternalChanges
223+
simpleFormProps={{
224+
defaultValues: { published: '2021-09-11 20:00:00' },
225+
}}
226+
/>
227+
);
228+
await screen.findByText('"2021-09-11 20:00:00" (string)');
229+
const input = screen.getByLabelText('Published') as HTMLInputElement;
230+
fireEvent.change(input, {
231+
target: { value: '2021-10-30 09:00:00' },
232+
});
233+
fireEvent.blur(input);
234+
await screen.findByText('"2021-10-30T09:00" (string)');
235+
fireEvent.click(screen.getByText('Reset'));
236+
await screen.findByText('"2021-09-11 20:00:00" (string)');
237+
});
238+
220239
describe('error message', () => {
221240
it('should not be displayed if field is pristine', () => {
222241
render(

packages/ra-ui-materialui/src/input/DateTimeInput.tsx

Lines changed: 25 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,6 @@ import { CommonInputProps } from './CommonInputProps';
77
import { sanitizeInputRestProps } from './sanitizeInputRestProps';
88
import { InputHelperText } from './InputHelperText';
99

10-
/**
11-
* Converts a datetime string without timezone to a date object
12-
* with timezone, using the browser timezone.
13-
*
14-
* @param {string} value Date string, formatted as yyyy-MM-ddThh:mm
15-
* @return {Date}
16-
*/
17-
const parseDateTime = (value: string) =>
18-
value ? new Date(value) : value === '' ? null : value;
19-
2010
/**
2111
* Input component for entering a date and a time with timezone, using the browser locale
2212
*/
@@ -32,7 +22,6 @@ export const DateTimeInput = ({
3222
onFocus,
3323
source,
3424
resource,
35-
parse = parseDateTime,
3625
validate,
3726
variant,
3827
disabled,
@@ -47,25 +36,35 @@ export const DateTimeInput = ({
4736
validate,
4837
disabled,
4938
readOnly,
39+
format,
5040
...rest,
5141
});
52-
const [renderCount, setRenderCount] = React.useState(1);
53-
const valueChangedFromInput = React.useRef(false);
5442
const localInputRef = React.useRef<HTMLInputElement>();
43+
// DateInput is not a really controlled input to ensure users can start entering a date, go to another input and come back to complete it.
44+
// This ref stores the value that is passed to the input defaultValue prop to solve this issue.
5545
const initialDefaultValueRef = React.useRef(field.value);
56-
const currentValueRef = React.useRef(field.value);
46+
// As the defaultValue prop won't trigger a remount of the HTML input, we will force it by changing the key.
47+
const [inputKey, setInputKey] = React.useState(1);
48+
// This ref let us track that the last change of the form state value was made by the input itself
49+
const wasLastChangedByInput = React.useRef(false);
5750

51+
// This effect ensures we stays in sync with the react-hook-form state when the value changes from outside the input
52+
// for instance by using react-hook-form reset or setValue methods.
5853
React.useEffect(() => {
59-
if (
60-
currentValueRef.current !== field.value &&
61-
!valueChangedFromInput.current
62-
) {
63-
setRenderCount(r => r + 1);
64-
initialDefaultValueRef.current = field.value;
65-
currentValueRef.current = field.value;
54+
// Ignore react-hook-form state changes if it came from the input itself
55+
if (wasLastChangedByInput.current) {
56+
// Resets the flag to ensure futures changes are handled
57+
wasLastChangedByInput.current = false;
58+
return;
6659
}
67-
valueChangedFromInput.current = false;
68-
}, [setRenderCount, parse, field]);
60+
61+
// The value has changed from outside the input, we update the input value
62+
initialDefaultValueRef.current = field.value;
63+
// Trigger a remount of the HTML input
64+
setInputKey(r => r + 1);
65+
// Resets the flag to ensure futures changes are handled
66+
wasLastChangedByInput.current = false;
67+
}, [setInputKey, field.value]);
6968

7069
const { onBlur: onBlurFromField } = field;
7170
const hasFocus = React.useRef(false);
@@ -91,8 +90,8 @@ export const DateTimeInput = ({
9190
// The input reset is handled in the onBlur event handler
9291
if (newValue !== '' && newValue != null && isNewValueValid) {
9392
field.onChange(newValue);
94-
valueChangedFromInput.current = true;
95-
currentValueRef.current = newValue;
93+
// Track the fact that the next react-hook-form state change was triggered by the input itself
94+
wasLastChangedByInput.current = true;
9695
}
9796
};
9897

@@ -137,7 +136,7 @@ export const DateTimeInput = ({
137136
inputRef={inputRef}
138137
name={name}
139138
defaultValue={format(initialDefaultValueRef.current)}
140-
key={renderCount}
139+
key={inputKey}
141140
type="datetime-local"
142141
onChange={handleChange}
143142
onFocus={handleFocus}

0 commit comments

Comments
 (0)