Skip to content

Commit e0a5db8

Browse files
committed
Add more comments and tests
1 parent bf72483 commit e0a5db8

File tree

2 files changed

+78
-10
lines changed

2 files changed

+78
-10
lines changed

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

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,34 @@ export const NonFullWidth = () => (
4646
</Wrapper>
4747
);
4848

49+
export const DefaultValue = () => (
50+
<Wrapper>
51+
All the displayed values should be the same: 2021-09-21 displayed in the
52+
browser locale
53+
{[
54+
'2021-09-11',
55+
'09/11/2011', // US date format
56+
'2021-09-11T20:46:20.000+02:00',
57+
'2021-09-11 20:46:20.000+02:00',
58+
'2021-09-11T20:46:20.000-04:00',
59+
'2021-09-11 20:46:20.000-04:00',
60+
'2021-09-11T20:46:20.000Z',
61+
'2021-09-11 20:46:20.000Z',
62+
new Date('2021-09-11T20:46:20.000+02:00'),
63+
// although this one is 2021-09-10, its local timezone makes it 2021-09-11 in the test timezone
64+
new Date('2021-09-10T20:46:20.000-04:00'),
65+
new Date('2021-09-11T20:46:20.000Z'),
66+
1631385980000,
67+
].map((defaultValue, index) => (
68+
<DateInput
69+
key={index}
70+
source={`publishedAt-${index}`}
71+
defaultValue={defaultValue}
72+
helperText={false}
73+
/>
74+
))}
75+
</Wrapper>
76+
);
4977
export const Disabled = () => (
5078
<Wrapper>
5179
<DateInput source="publishedAt" disabled />
@@ -66,6 +94,12 @@ export const Validate = () => (
6694
</Wrapper>
6795
);
6896

97+
export const Parse = () => (
98+
<Wrapper>
99+
<DateInput source="publishedAt" parse={value => new Date(value)} />
100+
</Wrapper>
101+
);
102+
69103
const i18nProvider = polyglotI18nProvider(() => englishMessages);
70104

71105
const Wrapper = ({

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

Lines changed: 44 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -24,15 +24,26 @@ import { InputHelperText } from './InputHelperText';
2424
* );
2525
*
2626
* @example
27+
* // If the initial value string contains more than a date (e.g. an hour, a timezone),
28+
* // these details are ignored.
29+
* <DateInput source="published_at" defaultValue="2021-09-11T20:46:20.000-04:00" />
30+
* // The input will display '2021-09-11' whatever the browser timezone.
31+
*
32+
* @example
2733
* // If the initial value is a Date object, DateInput converts it to a string
28-
* // but you must pass a custom parse method to convert the form value
29-
* // (which is always a date string) back to a Date object.
34+
* // and ignores the timezone.
35+
* <DateInput source="published_at" defaultValue={new Date("2021-09-11T20:46:20.000-04:00")} />
36+
* // The input will display '2021-09-11' whatever the browser timezone.
37+
*
38+
* @example
39+
* // If you want the returned value to be a Date, you must pass a custom parse method
40+
* to convert the form value (which is always a date string) back to a Date object.
3041
* <DateInput source="published_at" parse={val => new Date(val)} />
3142
*/
3243
export const DateInput = ({
3344
className,
3445
defaultValue,
35-
format = getStringFromDate,
46+
format = defaultFormat,
3647
label,
3748
source,
3849
resource,
@@ -61,6 +72,7 @@ export const DateInput = ({
6172
const localInputRef = React.useRef<HTMLInputElement>();
6273
const initialDefaultValueRef = React.useRef(field.value);
6374

75+
// update the react-hook-form value if the field value changes
6476
React.useEffect(() => {
6577
const initialDateValue =
6678
new Date(initialDefaultValueRef.current).getTime() || null;
@@ -82,8 +94,7 @@ export const DateInput = ({
8294
const hasFocus = React.useRef(false);
8395

8496
// Update the input text when the user types in the input.
85-
// This does not directly update the react-hook-form value,
86-
// which is updated on blur and by the useEffect above.
97+
// Also, update the react-hook-form value if the input value is a valid date string.
8798
const handleChange = useEvent(
8899
(event: React.ChangeEvent<HTMLInputElement>) => {
89100
if (onChange) {
@@ -102,7 +113,8 @@ export const DateInput = ({
102113
(target.valueAsDate != null &&
103114
!isNaN(new Date(target.valueAsDate).getTime()));
104115

105-
// Some browsers will return null for an invalid date so we only change react-hook-form value if it's not null
116+
// Some browsers will return null for an invalid date
117+
// so we only change react-hook-form value if it's not null.
106118
// The input reset is handled in the onBlur event handler
107119
if (newValue !== '' && newValue != null && isNewValueValid) {
108120
field.onChange(newValue);
@@ -193,7 +205,7 @@ export type DateInputProps = CommonInputProps &
193205
Omit<TextFieldProps, 'helperText' | 'label'>;
194206

195207
/**
196-
* Convert Date object to String
208+
* Convert Date object to String, ignoring the timezone.
197209
*
198210
* @param {Date} value value to convert
199211
* @returns {String} A standardized date (yyyy-MM-dd), to be passed to an <input type="date" />
@@ -211,23 +223,45 @@ const convertDateToString = (value: Date) => {
211223
const dateRegex = /^(\d{4}-\d{2}-\d{2}).*$/;
212224
const defaultInputLabelProps = { shrink: true };
213225

214-
const getStringFromDate = (value: string | Date) => {
226+
/**
227+
* Convert a form state value to a date string for the `<input type="date">` value.
228+
*
229+
* Form state values can be anything from:
230+
* - a string in the "YYYY-MM-DD" format
231+
* - A valid date string
232+
* - an ISO date string
233+
* - a Date object
234+
* - a Linux timestamp
235+
* - an empty string
236+
*
237+
* The output is always a string in the "YYYY-MM-DD" format.
238+
*
239+
* @example
240+
* defaultFormat('2021-09-11'); // '2021-09-11'
241+
* defaultFormat('09/11/2021'); // '2021-09-11'
242+
* defaultFormat('2021-09-11T20:46:20.000Z'); // '2021-09-11'
243+
* defaultFormat(new Date('2021-09-11T20:46:20.000Z')); // '2021-09-11'
244+
* defaultFormat(1631385980000); // '2021-09-11'
245+
* defaultFormat(''); // null
246+
*/
247+
const defaultFormat = (value: string | Date | number) => {
215248
// null, undefined and empty string values should not go through dateFormatter
216249
// otherwise, it returns undefined and will make the input an uncontrolled one.
217250
if (value == null || value === '') {
218251
return null;
219252
}
220253

254+
// Date objects should be converted to strings
221255
if (value instanceof Date) {
222256
return convertDateToString(value);
223257
}
224258

225-
// valid dates should not be converted
226-
// This regex only returns the date part of the provided string
259+
// Valid date strings should be stripped of their time and timezone parts.
227260
const matches = dateRegex.exec(value);
228261
if (matches) {
229262
return matches[1];
230263
}
231264

265+
// other values (e.g., localized date strings, timestamps) need to be converted to Dates first
232266
return convertDateToString(new Date(value));
233267
};

0 commit comments

Comments
 (0)