kojow7
kojow7

Reputation: 11384

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?

Upvotes: 6

Views: 441

Answers (3)

Divyanshu Maithani
Divyanshu Maithani

Reputation: 14976

Why do we need to store the component values in state?

We don't really need to store the component values in state and it's perfectly fine to access those values on form submit.

However, storing the component values in state has its own advantages. The main reason behind this is to make React state as the single source of truth. When the state is updated (on handleInputChange method), React checks the components (or specifically the parts of components or sub-trees) which needs to be re-rendered.

Using this approach, React helps us to achieve what was earlier achieved via Two Way Binding helpers. In short:

Form updated -> handleInputChange -> state updated -> updated state passed to other components accessing it

For example say, you have a <Card /> component which needs input from the user via a <Form /> component and you wish to display the information as the user types, then the best way would be update your state which would cause React to look for the sub-trees where it was accessed and re-render only those. Thus your <Card /> component will update itself as you type in the <Form />.

Also, React doesn't re-render everything for every single character typed but only those sub-trees which needs to reflect the text change. See the React diffing algorithm for how it is done. In this case, as you type in the characters in a particular field in the form, only those sub-trees in components will be re-rendered which display that field.


Execution of setState and asynchronous behavior

As mentioned by the React docs:

State Updates May Be Asynchronous

React may batch multiple setState() calls into a single update for performance.

Because this.props and this.state may be updated asynchronously, you should not rely on their values for calculating the next state.

Your code should work fine since you're not relying on the previous state to calculate the next state and also you're accessing this.state in a different block. So your state updates should be reflected fine; however if you don't want to think about the state updates (or suspect that your code might pick up previous state), React docs also mention an alternative method (which is actually better) that accepts a function rather than an object:

this.setState((prevState, props) => ({
  counter: prevState.counter + props.increment
}));

If you're still wondering when you can use the setState method safely, use it if other components don't rely on the state or when you don't need to persist the state (save in local storage or a server). When working with larger projects its always a good idea to make use of state containers to save yourself the hassle, like Redux.

Upvotes: 3

Alex Young
Alex Young

Reputation: 4039

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

Upvotes: 5

James Ganong
James Ganong

Reputation: 1171

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.

Upvotes: 3

Related Questions