react-final-form icon indicating copy to clipboard operation
react-final-form copied to clipboard

Async validator on unregister set validating to true

Open rvsia opened this issue 5 years ago • 8 comments

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

rvsia avatar Apr 17 '20 10:04 rvsia

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)

rvsia avatar Apr 17 '20 12:04 rvsia

I have the same issue

Chrisdo82 avatar Apr 23 '20 08:04 Chrisdo82

@erikras any thoughts?

rvsia avatar May 15 '20 09:05 rvsia

I have the same issue either

ArnoZx avatar Jun 22 '20 05:06 ArnoZx

@erikras is there a workaround to avoid this behaviour?

Chrisdo82 avatar Sep 24 '20 08:09 Chrisdo82

@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.)

rvsia avatar Sep 24 '20 15:09 rvsia

Any new solutions ?

bartek2341 avatar Jan 03 '21 15:01 bartek2341

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]);

kgregory avatar Nov 30 '21 16:11 kgregory

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

xxleyi avatar Feb 25 '23 10:02 xxleyi

@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);
}

xxleyi avatar Feb 25 '23 10:02 xxleyi