Skip to content

Commit 28920e9

Browse files
Fix 3352 by implementing useAltDateWidgetProps hook (#4820)
* Fix 3352 by implementing useAltDateWidgetProps hook Fixes #3352 by refactoring `AltDateWidget` in `core` to make the `useAltDateWidgetProps()` hook - In `@rjsf/core`, refactored the `DateElement`, date parsing/state and callbacks into the new `useAltDateWidgetProps()` hook in `@rjsf/utils` - In `@rjsf/antd` and `@rjsf/chakra-ui`, updated `AltDateWidget` to change `showTime` prop to `time`, using the `useAltDateWidgetProps()` hook - In `@rjsf/daisyui`, updated `AltDateWidget` to use the `useAltDateWidgetProps()` hook - In `@jrsf/utils` exported the `useAltDateWidgetProps()` hook & `DateElement` component and their associated types, including the `DateElementProp` in `getDateElementProps.ts` - Updated the `utility-functions.md` and `v6.x upgrade guide.md` to document the new hook - Updated the `CHANGELOG.md` accordingly # Conflicts: # CHANGELOG.md # packages/utils/src/index.ts * - Fixed little issues found during testing in playground * - Updated Mantine's AltDateWidget * Apply suggestions from code review - Responded to reviewer feedback Co-authored-by: Nick Grosenbacher <[email protected]> --------- Co-authored-by: Nick Grosenbacher <[email protected]>
1 parent 87517f9 commit 28920e9

File tree

15 files changed

+639
-665
lines changed

15 files changed

+639
-665
lines changed

CHANGELOG.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,16 @@ should change the heading of the (upcoming) version to include a major version b
1717
-->
1818
# 6.0.0-beta.23
1919

20+
## @rjsf/antd
21+
22+
- Updated `AltDateWidget` to use the new `useAltDateWidgetProps()` hook, renaming `showTime` to `time`
23+
- Potentially BREAKING CHANGE: Updated `AltDateTimeWidget` to rename `showTime` prop to `time`
24+
25+
## @rjsf/chakra-ui
26+
27+
- Updated `AltDateWidget` to use the new `useAltDateWidgetProps()` hook, renaming `showTime` to `time`
28+
- Potentially BREAKING CHANGE: Updated `AltDateTimeWidget` to rename `showTime` prop to `time`
29+
2030
## @rjsf/core
2131

2232
- Updated `FormProps` to add new `onChange`/`onBlur` values for the `liveValidate` and `liveOmit` props, deprecating the `boolean` aspect of them
@@ -27,19 +37,24 @@ should change the heading of the (upcoming) version to include a major version b
2737
- Updated `Form` to add a new programmatic function, `setFieldValue(fieldPath: string | FieldPathList, newValue?: T): void`, fixing [#2099](https://github.com/rjsf-team/react-jsonschema-form/issues/2099)
2838
- Added new `FallbackField` to add opt-in functionality to control form data that is of an unsupported or unknown type ([#4736](https://github.com/rjsf-team/react-jsonschema-form/issues/4736)).
2939
- Refactored much of the `FileWidget` implementation into a new `useFileWidgetProps()` hook, fixing [#3146](https://github.com/rjsf-team/react-jsonschema-form/issues/3146)
40+
- Refactored much of the `AltDateWidget` implementation into a new `useAltDateWidgetProps()` hook, fixing [#3352](http://github.com/rjsf-team/react-jsonschema-form/issues/3352)
3041

3142
## @rjsf/daisyui
3243

3344
- Deleted the `FileWidget` component, moving the className and isMulti logic directly into the `BaseInputTemplate` so that the `@rjsf/core`'s `FileWidget` works properly for the theme, fixing [#4803](https://github.com/rjsf-team/react-jsonschema-form/issues/4803)
45+
- Updated `AltDateWidget` to use the new `useAltDateWidgetProps()` hook
3446

3547
## @rjsf/mantine
3648

3749
- Updated `FieldHelpTemplate` to avoid issue when `help` `and `fieldPathId` are undefined
50+
- Updated `AltDateWidget` to use the new `useAltDateWidgetProps()` hook, renaming `showTime` to `time`
51+
- Potentially BREAKING CHANGE: Updated `AltDateTimeWidget` to rename `showTime` prop to `time`
3852
- Updated `FileWidget` to use the `useFileWidgetProps()` hook
3953

4054
## @rjsf/utils
4155

4256
- Added the `useFileWidgetProps()` hook implementation, refactored from `@rjsf/core`
57+
- Added the `useAltDateWidgetProps()` hook implementation, refactored from `@rjsf/core`
4358

4459
## Dev / docs / playground
4560

packages/antd/src/widgets/AltDateTimeWidget/index.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,10 @@ export default function AltDateTimeWidget<
88
F extends FormContextType = any,
99
>(props: WidgetProps<T, S, F>) {
1010
const { AltDateWidget } = props.registry.widgets;
11-
return <AltDateWidget showTime {...props} />;
11+
return <AltDateWidget time {...props} />;
1212
}
1313

1414
AltDateTimeWidget.defaultProps = {
1515
..._AltDateWidget.defaultProps,
16-
showTime: true,
16+
time: true,
1717
};
Lines changed: 21 additions & 107 deletions
Original file line numberDiff line numberDiff line change
@@ -1,135 +1,49 @@
1-
import { MouseEvent, useEffect, useState } from 'react';
21
import { Row, Col, Button } from 'antd';
32
import {
4-
ariaDescribedByIds,
5-
dateRangeOptions,
6-
getDateElementProps,
7-
parseDateString,
8-
toDateString,
9-
DateObject,
3+
DateElement,
104
FormContextType,
115
GenericObjectType,
126
RJSFSchema,
137
StrictRJSFSchema,
148
TranslatableString,
9+
useAltDateWidgetProps,
1510
WidgetProps,
16-
DateElementFormat,
1711
} from '@rjsf/utils';
1812

19-
type DateElementProps<T = any, S extends StrictRJSFSchema = RJSFSchema, F extends FormContextType = any> = Pick<
20-
WidgetProps<T, S, F>,
21-
'id' | 'name' | 'value' | 'disabled' | 'readonly' | 'autofocus' | 'registry' | 'onBlur' | 'onFocus'
22-
> & {
23-
select: (property: keyof DateObject, value: any) => void;
24-
type: string;
25-
range: [number, number];
26-
};
27-
28-
const readyForChange = (state: DateObject) => {
29-
return Object.values(state).every((value) => value !== -1);
30-
};
31-
3213
export default function AltDateWidget<
3314
T = any,
3415
S extends StrictRJSFSchema = RJSFSchema,
3516
F extends FormContextType = any,
3617
>(props: WidgetProps<T, S, F>) {
37-
const { autofocus, disabled, id, onBlur, onChange, onFocus, options, readonly, registry, showTime, value } = props;
38-
const { formContext, translateString, widgets } = registry;
39-
const { SelectWidget } = widgets;
18+
const { autofocus, disabled, id, name, onBlur, onFocus, options, readonly, registry } = props;
19+
const { formContext, translateString } = registry;
4020
const { rowGutter = 24 } = formContext as GenericObjectType;
41-
42-
const [state, setState] = useState(parseDateString(value, showTime));
43-
44-
useEffect(() => {
45-
setState(parseDateString(value, showTime));
46-
}, [showTime, value]);
47-
48-
const handleChange = (property: keyof DateObject, nextValue: any) => {
49-
const nextState = {
50-
...state,
51-
[property]: typeof nextValue === 'undefined' ? -1 : nextValue,
52-
};
53-
54-
if (readyForChange(nextState)) {
55-
onChange(toDateString(nextState, showTime));
56-
} else {
57-
setState(nextState);
58-
}
59-
};
60-
61-
const handleNow = (event: MouseEvent) => {
62-
event.preventDefault();
63-
if (disabled || readonly) {
64-
return;
65-
}
66-
const nextState = parseDateString(new Date().toJSON(), showTime);
67-
onChange(toDateString(nextState, showTime));
68-
};
69-
70-
const handleClear = (event: MouseEvent) => {
71-
event.preventDefault();
72-
if (disabled || readonly) {
73-
return;
74-
}
75-
onChange(undefined);
76-
};
77-
78-
const renderDateElement = (elemProps: DateElementProps<T, S, F>) => (
79-
<SelectWidget
80-
autofocus={elemProps.autofocus}
81-
className='form-control'
82-
disabled={elemProps.disabled}
83-
id={elemProps.id}
84-
name={elemProps.name}
85-
onBlur={elemProps.onBlur}
86-
onChange={(elemValue) => elemProps.select(elemProps.type as keyof DateObject, elemValue)}
87-
onFocus={elemProps.onFocus}
88-
options={{
89-
enumOptions: dateRangeOptions<S>(elemProps.range[0], elemProps.range[1]),
90-
}}
91-
placeholder={elemProps.type}
92-
readonly={elemProps.readonly}
93-
schema={{ type: 'integer' } as S}
94-
value={elemProps.value}
95-
registry={registry}
96-
label=''
97-
aria-describedby={ariaDescribedByIds(id)}
98-
/>
99-
);
21+
const { elements, handleChange, handleClear, handleSetNow } = useAltDateWidgetProps(props);
10022

10123
return (
10224
<Row gutter={[Math.floor(rowGutter / 2), Math.floor(rowGutter / 2)]}>
103-
{getDateElementProps(
104-
state,
105-
showTime,
106-
options.yearsRange as [number, number] | undefined,
107-
options.format as DateElementFormat | undefined,
108-
).map((elemProps, i) => {
109-
const elemId = id + '_' + elemProps.type;
25+
{elements.map((elemProps, i) => {
26+
const elemId = `${id}_${elemProps.type}`;
11027
return (
11128
<Col flex='88px' key={elemId}>
112-
{renderDateElement({
113-
...elemProps,
114-
autofocus: autofocus && i === 0,
115-
disabled,
116-
id: elemId,
117-
name: id,
118-
onBlur,
119-
onFocus,
120-
readonly,
121-
registry,
122-
select: handleChange,
123-
// NOTE: antd components accept -1 rather than issue a warning
124-
// like material-ui, so we need to convert -1 to undefined here.
125-
value: elemProps.value || -1 < 0 ? undefined : elemProps.value,
126-
})}
29+
<DateElement
30+
rootId={id}
31+
name={name}
32+
select={handleChange}
33+
{...elemProps}
34+
disabled={disabled}
35+
readonly={readonly}
36+
registry={registry}
37+
onBlur={onBlur}
38+
onFocus={onFocus}
39+
autofocus={autofocus && i === 0}
40+
/>
12741
</Col>
12842
);
12943
})}
13044
{!options.hideNowButton && (
13145
<Col flex='88px'>
132-
<Button block className='btn-now' onClick={handleNow} type='primary'>
46+
<Button block className='btn-now' onClick={handleSetNow} type='primary'>
13347
{translateString(TranslatableString.NowLabel)}
13448
</Button>
13549
</Col>
@@ -152,5 +66,5 @@ AltDateWidget.defaultProps = {
15266
yearsRange: [1900, new Date().getFullYear() + 2],
15367
},
15468
readonly: false,
155-
showTime: false,
69+
time: false,
15670
};

packages/chakra-ui/src/AltDateTimeWidget/AltDateTimeWidget.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,12 @@ function AltDateTimeWidget<T = any, S extends StrictRJSFSchema = RJSFSchema, F e
55
props: WidgetProps<T, S, F>,
66
) {
77
const { AltDateWidget } = props.registry.widgets;
8-
return <AltDateWidget {...props} showTime />;
8+
return <AltDateWidget {...props} time />;
99
}
1010

1111
AltDateTimeWidget.defaultProps = {
1212
..._AltDateWidget.defaultProps,
13-
showTime: true,
13+
time: true,
1414
};
1515

1616
export default AltDateTimeWidget;

packages/chakra-ui/src/AltDateWidget/AltDateWidget.tsx

Lines changed: 13 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -1,133 +1,58 @@
11
import { Box, Button, FieldsetRoot } from '@chakra-ui/react';
22
import {
3-
ariaDescribedByIds,
4-
DateElementFormat,
5-
DateObject,
6-
dateRangeOptions,
3+
DateElement,
4+
DateElementProp,
75
FormContextType,
8-
getDateElementProps,
9-
parseDateString,
106
RJSFSchema,
117
StrictRJSFSchema,
12-
toDateString,
138
TranslatableString,
9+
useAltDateWidgetProps,
1410
WidgetProps,
1511
} from '@rjsf/utils';
16-
import { MouseEvent, useEffect, useState } from 'react';
1712
import { getChakra } from '../utils';
1813

19-
function DateElement<T = any, S extends StrictRJSFSchema = RJSFSchema, F extends FormContextType = any>(
20-
props: WidgetProps<T, S, F>,
21-
) {
22-
const { SelectWidget } = props.registry.widgets;
23-
const value = props.value ? props.value : undefined;
24-
return (
25-
<SelectWidget
26-
{...props}
27-
label={''}
28-
className='form-control'
29-
onChange={(elemValue: WidgetProps<T, S, F>) => props.select(props.type, elemValue)}
30-
options={{
31-
enumOptions: dateRangeOptions<S>(props.range[0], props.range[1]),
32-
}}
33-
placeholder={props.type}
34-
schema={{ type: 'integer' } as S}
35-
value={value}
36-
aria-describedby={ariaDescribedByIds(props.name)}
37-
/>
38-
);
39-
}
40-
41-
interface AltDateStateType extends DateObject {
42-
[x: string]: number | undefined;
43-
}
44-
45-
const readyForChange = (state: AltDateStateType) => {
46-
return Object.keys(state).every((key) => typeof state[key] !== 'undefined' && state[key] !== -1);
47-
};
48-
4914
function AltDateWidget<T = any, S extends StrictRJSFSchema = RJSFSchema, F extends FormContextType = any>(
5015
props: WidgetProps<T, S, F>,
5116
) {
52-
const { autofocus, disabled, id, onBlur, onChange, onFocus, options, readonly, registry, showTime, value } = props;
17+
const { autofocus, disabled, id, onBlur, onFocus, options, readonly, registry } = props;
5318
const { translateString } = registry;
54-
const [state, setState] = useState(parseDateString(value, showTime));
55-
useEffect(() => {
56-
setState(parseDateString(value, showTime));
57-
}, [showTime, value]);
58-
59-
const handleChange = (property: string, nextValue: string) => {
60-
const nextState = {
61-
...state,
62-
[property]: typeof nextValue === 'undefined' ? -1 : nextValue,
63-
};
64-
65-
if (readyForChange(nextState)) {
66-
onChange(toDateString(nextState, showTime));
67-
} else {
68-
setState(nextState);
69-
}
70-
};
71-
72-
const handleNow = (event: MouseEvent<HTMLButtonElement>) => {
73-
event.preventDefault();
74-
if (disabled || readonly) {
75-
return;
76-
}
77-
const nextState = parseDateString(new Date().toJSON(), showTime);
78-
onChange(toDateString(nextState, showTime));
79-
};
80-
81-
const handleClear = (event: MouseEvent<HTMLButtonElement>) => {
82-
event.preventDefault();
83-
if (disabled || readonly) {
84-
return;
85-
}
86-
onChange(undefined);
87-
};
19+
const { elements, handleChange, handleClear, handleSetNow } = useAltDateWidgetProps(props);
8820

8921
const chakraProps = getChakra({ uiSchema: props.uiSchema });
9022

9123
return (
9224
<FieldsetRoot {...(chakraProps as any)}>
9325
<Box display='flex' flexWrap='wrap' alignItems='center'>
94-
{getDateElementProps(
95-
state,
96-
showTime,
97-
options.yearsRange as [number, number] | undefined,
98-
options.format as DateElementFormat | undefined,
99-
).map((elemProps: any, i) => {
100-
const elemId = id + '_' + elemProps.type;
26+
{elements.map((elemProps: DateElementProp, i) => {
27+
const elemId = `${id}_${elemProps.type}`;
10128
return (
102-
<Box key={elemId} mr='2' mb='2'>
29+
<Box key={elemId} mr='2' mb='2' width='20'>
10330
<DateElement<T, S, F>
10431
{...props}
10532
{...elemProps}
10633
autofocus={autofocus && i === 0}
10734
disabled={disabled}
108-
id={elemId}
35+
rootId={id}
10936
name={id}
11037
onBlur={onBlur}
11138
onFocus={onFocus}
11239
readonly={readonly}
11340
registry={registry}
11441
select={handleChange}
115-
value={elemProps.value < 0 ? '' : elemProps.value}
42+
value={elemProps.value && elemProps.value < 0 ? '' : elemProps.value}
11643
/>
11744
</Box>
11845
);
11946
})}
12047
</Box>
12148
<Box display='flex'>
12249
{!options.hideNowButton && (
123-
<Button onClick={(e: MouseEvent<HTMLButtonElement>) => handleNow(e)} mr='2'>
50+
<Button onClick={handleSetNow} mr='2'>
12451
{translateString(TranslatableString.NowLabel)}
12552
</Button>
12653
)}
12754
{!options.hideClearButton && (
128-
<Button onClick={(e: MouseEvent<HTMLButtonElement>) => handleClear(e)}>
129-
{translateString(TranslatableString.ClearLabel)}
130-
</Button>
55+
<Button onClick={handleClear}>{translateString(TranslatableString.ClearLabel)}</Button>
13156
)}
13257
</Box>
13358
</FieldsetRoot>
@@ -138,7 +63,7 @@ AltDateWidget.defaultProps = {
13863
autofocus: false,
13964
disabled: false,
14065
readonly: false,
141-
showTime: false,
66+
time: false,
14267
options: {
14368
yearsRange: [1900, new Date().getFullYear() + 2],
14469
},

0 commit comments

Comments
 (0)