Skip to content

Commit c6ef779

Browse files
authored
Merge pull request #10926 from marmelab/speed-up-arry-input
Speed up ArrayInput
2 parents 4200e46 + 89bedb0 commit c6ef779

File tree

18 files changed

+319
-204
lines changed

18 files changed

+319
-204
lines changed

examples/simple/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
"react": "^18.3.1",
2626
"react-admin": "^5.12.3",
2727
"react-dom": "^18.3.1",
28-
"react-hook-form": "^7.53.0",
28+
"react-hook-form": "^7.65.0",
2929
"react-router": "^6.28.1",
3030
"react-router-dom": "^6.28.1"
3131
},

packages/ra-core/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@
4848
"jscodeshift": "^0.15.2",
4949
"react": "^18.3.1",
5050
"react-dom": "^18.3.1",
51-
"react-hook-form": "^7.53.0",
51+
"react-hook-form": "^7.65.0",
5252
"react-router": "^6.28.1",
5353
"react-router-dom": "^6.28.1",
5454
"typescript": "^5.1.3",
@@ -59,7 +59,7 @@
5959
"peerDependencies": {
6060
"react": "^18.0.0 || ^19.0.0",
6161
"react-dom": "^18.0.0 || ^19.0.0",
62-
"react-hook-form": "^7.53.0",
62+
"react-hook-form": "^7.65.0",
6363
"react-router": "^6.28.1 || ^7.1.1",
6464
"react-router-dom": "^6.28.1 || ^7.1.1"
6565
},

packages/ra-core/src/controller/input/SimpleFormIteratorBase.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ export const SimpleFormIteratorBase = (props: SimpleFormIteratorBaseProps) => {
2323
);
2424
}
2525

26-
const { append, fields, move, remove, replace } = useArrayInput(props);
26+
const { append, fields, move, remove } = useArrayInput(props);
2727
const { trigger, getValues } = useFormContext();
2828

2929
const removeField = useEvent((index: number) => {
@@ -47,7 +47,7 @@ export const SimpleFormIteratorBase = (props: SimpleFormIteratorBaseProps) => {
4747
});
4848

4949
const handleArrayClear = useEvent(() => {
50-
replace([]);
50+
remove();
5151
});
5252

5353
const context = useMemo(

packages/ra-core/src/core/SourceContext.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ export type SourceContextValue = {
3333
export const SourceContext = createContext<SourceContextValue | undefined>(
3434
undefined
3535
);
36+
SourceContext.displayName = 'SourceContext';
3637

3738
const defaultContextValue = {
3839
getSource: (source: string) => source,

packages/ra-core/src/form/groups/FormGroupsContext.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { createContext } from 'react';
33
export const FormGroupsContext = createContext<
44
FormGroupsContextValue | undefined
55
>(undefined);
6+
FormGroupsContext.displayName = 'FormGroupsContext';
67

78
export type FormGroupSubscriber = () => void;
89

packages/ra-core/src/form/useApplyInputDefaultValues.ts

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useEffect } from 'react';
1+
import { useEffect, useRef } from 'react';
22
import {
33
FieldValues,
44
UseFieldArrayReturn,
@@ -36,12 +36,21 @@ export const useApplyInputDefaultValues = ({
3636
const finalSource = useWrappedSource(source);
3737

3838
const record = useRecordContext(inputProps);
39-
const { getValues, resetField, formState, reset } = useFormContext();
39+
const { getValues, resetField, reset, subscribe } = useFormContext();
4040
const recordValue = get(record, finalSource);
4141
const formValue = get(getValues(), finalSource);
42-
const { dirtyFields } = formState;
43-
const isDirty = Object.keys(dirtyFields).includes(finalSource);
42+
const isDirty = useRef<boolean | undefined>(undefined);
4443

44+
useEffect(() => {
45+
return subscribe({
46+
// Even though we only need dirtyFields, we subscribe to values as well to
47+
// ensure we properly receive dirtyFields updates for newly added items in an ArrayInput
48+
formState: { values: true, dirtyFields: true },
49+
callback: ({ dirtyFields }) => {
50+
isDirty.current = get(dirtyFields ?? {}, finalSource, false);
51+
},
52+
});
53+
}, [finalSource, subscribe]);
4554
useEffect(() => {
4655
if (
4756
defaultValue == null ||
@@ -52,7 +61,7 @@ export const useApplyInputDefaultValues = ({
5261
// We check strictly for undefined to avoid setting default value
5362
// when the field is null
5463
recordValue !== undefined ||
55-
isDirty
64+
isDirty.current === true
5665
) {
5766
return;
5867
}

packages/ra-core/src/form/useAugmentedForm.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,20 +76,24 @@ export const useAugmentedForm = <RecordType = any>(
7676

7777
const form = useForm({
7878
criteriaMode,
79-
values: defaultValuesIncludingRecord,
79+
defaultValues: defaultValuesIncludingRecord,
8080
reValidateMode,
8181
resolver: finalResolver,
8282
...rest,
8383
});
8484

8585
const formRef = useRef(form);
86+
const { reset } = form;
87+
88+
useEffect(() => {
89+
reset(defaultValuesIncludingRecord);
90+
}, [defaultValuesIncludingRecord, reset]);
8691

8792
// notify on invalid form
8893
useNotifyIsFormInvalid(form.control, !disableInvalidFormNotification);
8994

9095
const recordFromLocation = useRecordFromLocation();
9196
const recordFromLocationApplied = useRef(false);
92-
const { reset } = form;
9397
useEffect(() => {
9498
if (recordFromLocation && !recordFromLocationApplied.current) {
9599
reset(merge({}, defaultValuesIncludingRecord, recordFromLocation), {

packages/ra-core/src/form/useInput.stories.tsx

Lines changed: 42 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import * as React from 'react';
2+
import { useForm, FormProvider, useFieldArray } from 'react-hook-form';
23
import { CoreAdminContext } from '../core';
34
import { Form } from './Form';
45
import { InputProps, useInput } from './useInput';
@@ -7,12 +8,15 @@ export default {
78
title: 'ra-core/form/useInput',
89
};
910

10-
const Input = (props: InputProps) => {
11+
const Input = (props: InputProps & { log?: boolean }) => {
12+
const { label, log } = props;
1113
const { id, field, fieldState } = useInput(props);
12-
14+
if (log) {
15+
console.log(`Input ${id} rendered:`);
16+
}
1317
return (
1418
<label htmlFor={id}>
15-
{id}: <input id={id} {...field} />
19+
{label ?? id}: <input id={id} {...field} />
1620
{fieldState.error && <span>{fieldState.error.message}</span>}
1721
</label>
1822
);
@@ -86,3 +90,38 @@ DefaultValue.argTypes = {
8690
control: { type: 'select' },
8791
},
8892
};
93+
94+
export const Large = () => {
95+
const [submittedData, setSubmittedData] = React.useState<any>();
96+
const fields = Array.from({ length: 15 }).map((_, index) => (
97+
<Input
98+
key={index}
99+
source={`field${index + 1}`}
100+
label={`field${index + 1}`}
101+
/>
102+
));
103+
return (
104+
<CoreAdminContext>
105+
<Form
106+
onSubmit={data => setSubmittedData(data)}
107+
record={Array.from({ length: 15 }).reduce((acc, _, index) => {
108+
acc[`field${index + 1}`] = `value${index + 1}`;
109+
return acc;
110+
}, {})}
111+
>
112+
<div
113+
style={{
114+
display: 'flex',
115+
flexDirection: 'column',
116+
gap: '1em',
117+
marginBottom: '1em',
118+
}}
119+
>
120+
{fields}
121+
</div>
122+
<button type="submit">Submit</button>
123+
</Form>
124+
<pre>{JSON.stringify(submittedData, null, 2)}</pre>
125+
</CoreAdminContext>
126+
);
127+
};

packages/ra-core/src/test-ui/SimpleFormIterator.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -199,13 +199,13 @@ export const SimpleFormIterator = (props: SimpleFormIteratorProps) => {
199199
}
200200

201201
const [confirmIsOpen, setConfirmIsOpen] = useState<boolean>(false);
202-
const { fields, replace } = useArrayInput(props);
202+
const { fields, remove } = useArrayInput(props);
203203
const translate = useTranslate();
204204

205205
const handleArrayClear = useCallback(() => {
206-
replace([]);
206+
remove();
207207
setConfirmIsOpen(false);
208-
}, [replace]);
208+
}, [remove]);
209209

210210
const records = useFieldValue({ source: finalSource });
211211

packages/ra-input-rich-text/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@
5656
"ra-ui-materialui": "^5.12.3",
5757
"react": "^18.3.1",
5858
"react-dom": "^18.3.1",
59-
"react-hook-form": "^7.53.0",
59+
"react-hook-form": "^7.65.0",
6060
"tippy.js": "^6.3.7",
6161
"typescript": "^5.1.3",
6262
"zshy": "^0.4.4"

0 commit comments

Comments
 (0)