Async validator on unregister set validating to true
Are you submitting a bug report or a feature request?
bug report
What is the current behavior?
On Wizard page we have a step with a field, that has async validator.
After going to next step, the field is unregistered and a new field is registered with a not-async validator.
However, the validating is set to true when entering the step and after it's changed to false, the FormSpy component is not updated and re-rendered. So, the next button remains disabled.
This behavior was introduced in https://github.com/final-form/react-final-form/pull/766
What is the expected behavior?
When validating is set to false, re-render FormSpy
Sandbox Link
https://codesandbox.io/s/react-final-form-simple-example-0rc9g?fontsize=14&hidenavigation=1&theme=dark
Code
### code
import React from "react";
import { render } from "react-dom";
import Styles from "./Styles";
import { Form, Field, FormSpy } from "react-final-form";
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));
const asyncValidator = () => new Promise(res => setTimeout(() => res(), 1000));
const requiredValidator = value => (value ? "" : "required");
class App extends React.Component {
state = { mounted: false };
componentDidMount() {
this.setState({ mounted: true, step: 0 });
}
render() {
return (
<Styles>
<Form
onSubmit={
this.state.mounted
? async values => {
await sleep(300);
window.alert(JSON.stringify(values, 0, 2));
}
: () =>
window.alert(
"You are incorrectly calling an outdated onSubmit"
)
}
render={({ handleSubmit, reset, submitting, pristine, values }) => (
<form onSubmit={handleSubmit}>
<div>
{this.state.step === 0 && (
<React.Fragment>
<label>First Name</label>
<Field
name="firstName"
component="input"
type="text"
placeholder="First Name"
validate={asyncValidator}
/>
</React.Fragment>
)}
{this.state.step === 1 && (
<React.Fragment>
<label>Password Requiered</label>
<Field
name="password"
component="input"
type="text"
placeholder="12345"
validate={requiredValidator}
/>
</React.Fragment>
)}
</div>
<FormSpy>
{({ validating, submitting, pristine, valid }) => (
<div className="buttons">
{this.state.step === 0 && (
<button
type="button"
onClick={() => this.setState({ step: 1 })}
disabled={validating || !valid || pristine}
>
Next
</button>
)}
{this.state.step === 1 && (
<button
type="submit"
disabled={validating || submitting || pristine}
>
Submit
</button>
)}
<button
type="button"
onClick={reset}
disabled={submitting || pristine}
>
Reset
</button>
<pre>
{JSON.stringify({ values, validating, valid }, 0, 2)}
</pre>
</div>
)}
</FormSpy>
</form>
)}
/>
</Styles>
);
}
}
render(<App />, document.getElementById("root"));
What's your environment?
"final-form": "^4.19.1",
"final-form-arrays": "^3.0.2",
"final-form-focus": "^1.1.2",
"react-final-form": "^6.4.0",
"react-final-form-arrays": "^3.1.1"
Node v10.16.0
Issue https://github.com/data-driven-forms/react-forms/issues/431
cc @Hyperkid123
This error appears when entering the step with the async validate:
Uncaught (in promise) TypeError: Cannot read property 'active' of undefined
at publishFieldState (final-form.es.js:181)
at notifyField (final-form.es.js:734)
at notifyFieldListeners (final-form.es.js:745)
at notify (final-form.es.js:1065)
at afterPromise (final-form.es.js:707)
I have the same issue
@erikras any thoughts?
I have the same issue either
@erikras is there a workaround to avoid this behaviour?
@Chrisdo82
Not a great workaround (actually it's a terrible one :smiley:), but after the unregistering the field with a async validation we trigger form.change('nonsense', 'nonsense') in a timeout to rerender the form. (Just be sure you don't send the value in your submit.)
Any new solutions ?
Found a decent workaround in @alanpoulain's pull request
const form = useForm();
// Pause the validation while the Final Form field is registered to prevent a form state desynchronization bug when using async validators.
// Since the field is not registered directly because of the introspection, Final Form is using the previous form state (without the field) when notifying after the async validation done during the registration.
// See also https://github.com/final-form/react-final-form/issues/780.
form.pauseValidation();
useEffect(() => {
form.resumeValidation();
}, [form]);
Found a decent workaround in @alanpoulain's pull request
const form = useForm(); // Pause the validation while the Final Form field is registered to prevent a form state desynchronization bug when using async validators. // Since the field is not registered directly because of the introspection, Final Form is using the previous form state (without the field) when notifying after the async validation done during the registration. // See also https://github.com/final-form/react-final-form/issues/780. form.pauseValidation(); useEffect(() => { form.resumeValidation(); }, [form]);
not work in normal component
@Chrisdo82
Not a great workaround (actually it's a terrible one 😃), but after the unregistering the field with a async validation we trigger
form.change('nonsense', 'nonsense')in a timeout to rerender the form. (Just be sure you don't send the value in your submit.)
Inspired by this workaround, is using such a workaround:
// use change to trigger final form to run validation again
function forceValidate(form: FormApi<any>) {
const randomName = 'a' + Math.random().toString(36).slice(2);
form.change(randomName, 'a');
form.change(randomName, undefined);
}