bug report
When using FormSpy, the onChange is called during rendering, and throws an error if you use setState.
<FormSpy
subscription={{ values: true }}
onChange={change => {
// fired during rendering, calling a `useState` setter fails
setValues(change.values);
}}
/>
Warning: Cannot update a component (`App`) while rendering a different component (`FormSpy`). To locate the bad setState() call inside `FormSpy`, follow the stack trace as described in https://fb.me/setstate-in-render
in FormSpy (at App.js:23)
in form (at App.js:13)
in ReactFinalForm (at App.js:10)
in App (at src/index.js:9)
in StrictMode (at src/index.js:8)
No error.
https://codesandbox.io/s/react-final-form-formspy-hltnv?file=/src/App.js
"final-form": "4.20.0",
"react": "16.13.1",
"react-dom": "16.13.1",
"react-final-form": "6.5.0",
The issue seems to be that onChange in the useFormState is called in the useState init function, which means during rendering:
https://github.com/final-form/react-final-form/blob/464f1c7855e93899630df0ad897c322995601849/src/useFormState.js#L26
This is part of a range of error messages introduced in react 16.13, and related to an existing issue (https://github.com/final-form/react-final-form/issues/751)
A workaround would be to put any setState on the onChange in a setTimeout(... ,0), but this feels like a hack, and not clear to new users.
I am trying to read outside of the form the state saved in Redux and I got the same warning :
Related issue: #828
Is there a workaround or am I stuck with it?
@erikras Please any help on this? While the changes you published to #751 seem to work, I don't think it covers setting state inside the onChange function in a FormSpy.
Thanks.
The issue still persists with [email protected] and [email protected] with react version 16.13.1. Here's what I've found after digging through the (compiled) library source:
useField() is invoked internally by the react-final-form library from within the Field component.useState() hook. This parametrizes the internal register() function with a true second argument which is the silent flag. All is fine at this point.final-form goes into a validation loop. I see in the stack trace that it invokes runValidation() which in turn leads to notifyFieldListeners() and then to notifySubscribers() on each field. This actually brings the code path back to react-final-form, into the useEffect() call inside of which the field is registered again but this time without the silent flag.javascript
React.useEffect(function () {
return register(function (state) {
if (firstRender.current) {
firstRender.current = false;
} else {
setState(state);
}
}, false);
}
I think the following happens: from the aspect of one particular field, registration indeed happens properly (first with the silent and then without the silent flag) but other fields could have completed this cycle already. So, when a field gets initialized it runs into the validation loop, causing the setState() call to take place inside other fields, thereby violating the rule.
I have no suggestion at this point as to how it could be solved though.
Most helpful comment
The issue still persists with
[email protected]and[email protected]with react version16.13.1. Here's what I've found after digging through the (compiled) library source:useField()is invoked internally by thereact-final-formlibrary from within theFieldcomponent.useState()hook. This parametrizes the internalregister()function with atruesecond argument which is thesilentflag. All is fine at this point.final-formgoes into a validation loop. I see in the stack trace that it invokesrunValidation()which in turn leads tonotifyFieldListeners()and then tonotifySubscribers()on each field. This actually brings the code path back toreact-final-form, into theuseEffect()call inside of which the field is registered again but this time without thesilentflag.javascript React.useEffect(function () { return register(function (state) { if (firstRender.current) { firstRender.current = false; } else { setState(state); } }, false); }I think the following happens: from the aspect of one particular field, registration indeed happens properly (first with the silent and then without the silent flag) but other fields could have completed this cycle already. So, when a field gets initialized it runs into the validation loop, causing the
setState()call to take place inside other fields, thereby violating the rule.I have no suggestion at this point as to how it could be solved though.