Race condition between useFieldArray and reset, useFieldArray continues to use initial defaultValues after reset
See original GitHub issueDescribe the bug
My team has a collection of forms which utilise all the same data loading and submission APIs, but have slightly different form content and data transformation needs. To keep our code DRY we use 2 components to render a form:
- a top-level “FormContainer” which calls useForm and sets up all the data fetching & submission, plus some other concerns outside the form
- an inner “FormContent” which handles transformation of the form data and rendering of the fields.
So responsibilities are divided up like this:
<FormContainer />handles- data fetch
useForm(initialised with empty defaultValues)<FormProvider />- submission
- renders Spinner or FormContent based on loading states
<FormContent />- is passed data as a prop
- transforms data as needed
- calls
useEffect -> resetanduseFieldArray - renders the fields.
The bug: after structuring the forms this way we found that the line items we show with useFieldArray were not updating from the original default value once the data loaded.
the workaround: it turns out that the order of useEffect->reset and useFieldArray matters when hook-form is used in this way due to how resetFieldArrayFunctionRef is initialised internally. Basically useFieldArray has to be initialised before the useEffect resets the data or else it will continue consuming the original default values.
To Reproduce
This sandbox contains both the bug and a fix (comment/uncomment the relevant lines) https://codesandbox.io/s/react-hook-form-race-condition-bug-ytly7
// Uncomment this for bug
useEffect(() => {
if (data) {
reset(data);
}
}, [data, reset]);
const { fields, remove } = useFieldArray({
control,
name: "names.test"
});
// Uncomment this for fix
// useEffect(() => {
// if (data) {
// reset(data);
// }
// }, [data, reset]);
Other comments
While finding this bug was definitely developer error on my part, and the fix was fairly straight-forward, it was extremely non-obvious what was wrong, and I had to go through hook-form’s build and add logs before I realised that reset didn’t have a reference to useFieldArray’s reset method at the time of calling reset.
Ideally there would be a way to fix the race condition so that useFieldArray picks up recently reset values when its own effects run, as I can imagine there’s a useEffect internally which is getting queued by React in the order of calling and hook-form was not written with this condition in mind.
Worst case, perhaps a warning in the console or documentation would do, that reset can only be queued after all field arrays have been initialised?
Issue Analytics
- State:
- Created 3 years ago
- Comments:8 (5 by maintainers)
Top Related StackOverflow Question
@Moshyfawn Oh wow, I wish I could. The issues started with a really complicated form. Maybe I am able to isolate the issue better. But at the moment I am struggling a bit.
Basically new data is loaded async from the backend when a button is clicked. When the data is received, the effect for resetting the form is triggered. Now there is really much going on on the page, so its initially slow. And the data is randomly updated correctly or merged together with the old form state. What I already found out is, that resetting the form in useLayoutEffect is the only way to avoid this issue…
I think it has nothing to do with the strict mode. Because the issues are also there in production mode.
But the issue comes from within the forms state. The new data and the old somehow gets merged together. The new array entries replace the old ones. But if the new array is shorter than the old one, the old entries are still there in the next state.
Thanks for looking into this, I totally understand it’s difficult to fix.
Documentation change looks good, thanks for the hard work on this project! 👌