Skip to content

Instantly share code, notes, and snippets.

@tmkelly28
Last active July 6, 2017 20:33
Show Gist options
  • Save tmkelly28/09f608984cf79d2eee718773f519a4d1 to your computer and use it in GitHub Desktop.
Save tmkelly28/09f608984cf79d2eee718773f519a4d1 to your computer and use it in GitHub Desktop.

First, checkout this section of the React docs: https://facebook.github.io/react/docs/forms.html#handling-multiple-inputs. If you totally grokked that section of the docs, then you're all set! If you're still unsure about what was going on there, this is for you!


I very often see folks get tripped up when there are multiple form inputs to handle in a single component.

For example, consider a Login form. There's a good chance it will look some thing like this:

class Login extends Component {

  render () {
    return (
      <form>
      
        <label>Email</label>
        <input type="text" name="email" />
        
        <label>Password</label>
        <input type="password" name="password" />
        
      </form>
    );
  }
}

Naturally, we want to handle the state of these two input fields, so we might set aside two fields on this component's state.

class Login extends Component {

  constructor () {
    super();
    this.state = {
      email: '',
      password: ''
    };
  }

  render () {
    return (
      <form>
      
        <label>Email</label>
        <input type="text" name="email" />
        
        <label>Password</label>
        <input type="password" name="password" />
        
      </form>
    );
  }
}

Now, of course, we need to collect the values in those input fields using an onChange listener.

This is where the trouble starts. We see two input fields, so our knee-jerk reaction might be to create two change handlers.

class Login extends Component {

  constructor () {
    super();
    this.state = {
      email: '',
      password: ''
    };
    this.handleEmailChange = this.handleEmailChange.bind(this);
    this.handlePasswordChange = this.handlePasswordChange.bind(this);
  }
  
  handleEmailChange (evt) {
    this.setState({ email: evt.target.value });
  }
  
  handlePasswordChange (evt) {
    this.setState({ password: evt.target.value });
  }

  render () {
    return (
      <form>
      
        <label>Email</label>
        <input type="text" name="email" onChange={this.handleEmailChange} />
        
        <label>Password</label>
        <input type="password" name="password" onChange={this.handlePasswordChange} />
        
      </form>
    );
  }
}

This will work but...ew, right? What if we have a whole slew of different fields? Do we really want to write a new change handler for each?

It's actually very easy to write a generalized change handler - we just need two ingredients:

  1. The name of the form element
  2. The ability to use bracket notation to define our new state object.

Let's focus on number 2. In particular, there's an ES6 feature that will help make this even easier, so let's learn about that first.


As of ES6, it is possible to use bracket notation to assign keys to objects in object literals.

Right now, we know what it looks like to use bracket notation to assign a key when the object is stored in a variable.

const obj = {};

obj['someKey'] = 1;

console.log(obj.someKey); // 1

Remember that the advantage of bracket notation is that we can calculate a JavaScript expression within the brackets.

const obj = {};

obj['keyNo' + '2'] = 2;

console.log(obj.keyNo2); // 2

However, so far we've only been able to do this as long as our object is in a variable. Now we can use brackets when defining the object literal, like so:

const obj = {

  ['keyNo' + 3]: 3, // wowzers!
  
  regularKey: 4 // a regular key assignment, for comparision
};

console.log(obj.keyNo3); // 3
console.log(obj.regularKey); // 4

Now that we have more flexibility in using bracket syntax, let's return out attention to the first ingredient: the name of the form element.

As you may have noticed, regular <input> elements often have a property called "name":

<input type="text" name="email" />

This "name" property is also part of HTML - it's not particular to React.

We can access this name property from the event object that we receive from an event handler:

onChange (event) {
  console.log(event.target.name); // the name of the form element
  console.log(event.target.value); // the value of the form element
}

Now, let's return to our example. You may notice something convenient: the key names on our state object:

this.state = { email: '', password: ''}

...match up with the names of our inputs:

<input type="text" name="email" /> <input type="password" name="password" />

This means we can leverage the two ingredients we introduced above, and write a generalized change handler, like so:

class Login extends Component {

  constructor () {
    super();
    this.state = {
      email: '',
      password: ''
    };
    this.handleChange = this.handleChange.bind(this);
  }
  
  handleChange (evt) {
    // check it out: we get the evt.target.name (which will be either "email" or "password")
    // and use it to target the key on our `state` object with the same name, using bracket syntax
    this.setState({ [evt.target.name]: evt.target.value });
  }
  
  render () {
    return (
      <form>
      
        <label>Email</label>
        <input type="text" name="email" onChange={this.handleChange} />
        
        <label>Password</label>
        <input type="password" name="password" onChange={this.handleChange} />
        
      </form>
    );
  }
}

Much better! If you find yourself writing multiple change handlers in a single form, stop and consider how you can condense it into one.

Anticipated questions:

Q. Does this mean that the name of the key on my state object needs to match up with the name of my input?

A. Yes! If they had different names, we wouldn't be able to tell which field we wanted change when we setState. Make sure they match!

Q. Is there a way to do this without using ES6?

A. Yes - but it's not as clean.

ES6

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

NON-ES6 equivalent

handleChange(evt) {
  const newState = {};
  newState[evt.target.name] = evt.target.value;
  this.setState(newState);
}

Three lines of code or one line - the choice is yours, but I know which one I prefer.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment