-3

I have a form which should display an error if the form input is not of a certain format (not a url).

However currently when an invalid input is submitted, the form does not show the error until the form is submitted twice.

I think this is due to setState() being asynchronous, but even when using a functional setState() it does not seem to behave as expected.

What is the correct way to implement this behavior?

example

https://codesandbox.io/s/6w1vnovrm3

Colin Ricardo
  • 16,488
  • 11
  • 47
  • 80
  • [working example](https://codesandbox.io/s/1oq23w62wq), for reason check this answer: [setState behaviour](https://stackoverflow.com/questions/42593202/why-calling-setstate-method-doesnt-mutate-the-state-immediately/42593250#42593250) – Mayank Shukla Mar 17 '18 at 16:04

1 Answers1

1

I think this is due to setState() being asynchronous

You are completely right.

before checkUrl finishes setState the code below has already been executed:

const { errorMessage } = this.state;

if (this.state.error) {
  this.setState({
    helperText: errorMessage
  });
}

Fix:

handleSubmit = e => {
    e.preventDefault();
    const { url } = this.state;
    const error = this.checkUrl(url);

    if(error) {
      this.setState({
        helperText: error,
        errorMessage: error,
        error: true,
      });
    }
};

checkUrl = url => {
    if (!validUrl.isWebUri(url)) {
      return "please enter a valid url";
    }

    return null;
};

You probably aren't gonna need both errorMessage and helperText in the state.

EDIT:

Because setState is asynchronous you would have to use setState callbacks to avoid errors like:

handleSubmit = e => {
  e.preventDefault();
  const { url } = this.state;
  if (!validUrl.isWebUri(url)) {
    this.setState({ error: true, errorMessage: "please enter a valid url" }, () => {
      // ... this callback is fired when react has updated the state
      const { errorMessage } = this.state;
      if (this.state.error) {
        this.setState({
          helperText: errorMessage
        });
      }
    });
  }
};

but this is just overcomplicating.

Tomasz Mularczyk
  • 34,501
  • 19
  • 112
  • 166
  • This makes sense, thank you. So it's not possible to set the `helperText` based on if `error === true` and then use the `errorMessage` from state? – Colin Ricardo Mar 17 '18 at 16:11
  • it is, you will need to use `setState` callback which fires after state has been updated. I will update the answer... – Tomasz Mularczyk Mar 17 '18 at 16:14
  • @Tomasz This is cool, but another problem may occur because of setState not being synchronous: if you modify the URL and immediately hit enter, then the 'const { url } = this.state;' statement may be executed before the last change to the url has occurred in 'this.setState({ url: e.target.value });'. Not sure how this can be solved... Here is a question I asked regarding this: https://stackoverflow.com/questions/49264268/can-i-avoid-submission-of-a-react-form-before-the-state-of-all-controlled-fields – Yossi Mar 17 '18 at 18:22
  • @Rahamin true. I guess that's very marginal situation. To avoid it, however, I would set `name` on inputs and use `e.currentTarget` to access inputs and their values - `e.currentTarget.elements["input_name"].value`. – Tomasz Mularczyk Mar 17 '18 at 18:38