Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Understanding ReactJS Controlled form components

I am implementing the following code based on the following page: https://facebook.github.io/react/docs/forms.html

class Reservation extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      isGoing: true,
      numberOfGuests: 2
    };

    this.handleInputChange = this.handleInputChange.bind(this);
    this.handleSubmit = this.handleSubmit.bind(this);
  }

  handleInputChange(event) {
    const target = event.target;
    const value = target.type === 'checkbox' ? target.checked : target.value;
    const name = target.name;

    this.setState({
      [name]: value
    });
  }

  handleSubmit(event) {
    event.preventDefault();

    let data = {
      isGoing: this.state.isGoing,
      numberOfGuests: this.state.numberofGuests
    }

    /* Send data in ajax request here */

  }

  render() {
    return (
      <form onSubmit={this.handleSubmit}>
        <label>
          Is going:
          <input
            name="isGoing"
            type="checkbox"
            checked={this.state.isGoing}
            onChange={this.handleInputChange} />
        </label>
        <br />
        <label>
          Number of guests:
          <input
            name="numberOfGuests"
            type="number"
            value={this.state.numberOfGuests}
            onChange={this.handleInputChange} />
        </label>
        <input type="submit" value="Submit" />
      </form>
    );
  }
}

Some questions I have about it:

  1. Why do we need to store the component values in state? Why not just grab the values we need when the form is submitted as normally would be done with standard JavaScript? The recommended way would seem to reload the render function for every single character typed in or deleted. To me this makes little sense.
  2. Since the execution of setState is asynchronous and there may be a delay between this.setState() and actually accessing the state with this.state.numberOfGuests does this mean that this code may end up grabbing the state before it has been set? If so, why is this code being suggested in the official React docs? If not, why not?
like image 300
kojow7 Avatar asked Mar 24 '17 23:03

kojow7


2 Answers

Regarding point number two, then yes it is logically possible that handleSubmit could run before the state update in handleInputChanged has completed. The reason this isn't mentioned in the React docs, or is generally a concern for anyone is because the setState function runs really quickly. As an experiment I made a codepen to determine the average time taken for setState to run. It seems to take in the order of around 0.02 milliseconds. There is no way someone can change their input, then submit the form in less than that time. In fact, the e.preventDefault() call in handleSubmit takes nearly a quarter of that time anyway.

If you have a situation where it is absolutely crucial that setState has completed before continuing, then you can use a callback function to setState, e.g.

this.setState({
    colour: 'red'
}, () => {
    console.log(this.state.color)
});

Then red will always be logged, as opposed to the following where the previous value may be logged.

this.setState({
    colour: 'red'
});
console.log(this.state.color);
like image 60
Alex Young Avatar answered Nov 20 '22 18:11

Alex Young


Very good questions! Here's my take on them:

1. Controlled or Uncontrolled - that is the question

You don't have to use controlled form elements. You can use uncontrolled and grab the values as you suggest in your onFormSubmit handler by doing something like event.isGoing.value - plain ole JavaScript (or use refs as some React articles suggest). You can even set a default value with uncontrolled no problem by using, you guessed it, defaultValue={myDefaultValue}.

The above being said, one reason to use controlled components would be if you're looking to give real time feedback while the user is still typing. Say you need to do an autocomplete lookup or provide validation like password strength. Having a controlled component that re-renders with the values in the state makes this super simple.

2. this.setState() asynchronous issues?

[Maybe incorrectly,] I view internally component state updates more like a queue system. No call to this.setState() will be lost and shouldn't overwrite another one when dealing with synchronous code. However, there could be a time where a render is running behind a setState update, but it will eventually have and render the most recent value. Ex: the user types 3 characters, but they only see 2, then a short time later they should see the third. So, there was a point in time where the read to this.state read an "old" value, but it was still eventually updated. I hope I'm making sense here.

Now, I mention synchronous code above because with asynchronous code (like with AJAX) you could potentially introduce a race condition where this.setState() overwrites a newer state value.

like image 3
James Ganong Avatar answered Nov 20 '22 16:11

James Ganong