156

Curious what the right way to approach this is:

var Hello = React.createClass({
getInitialState: function() {
    return {total: 0, input1:0, input2:0};
},
render: function() {
    return (
        <div>{this.state.total}<br/>
            <input type="text" value={this.state.input1} onChange={this.handleChange} />
            <input type="text" value={this.state.input2} onChange={this.handleChange} />
        </div>
    );
},
handleChange: function(e){
    this.setState({ ??? : e.target.value});
    t = this.state.input1 + this.state.input2;
    this.setState({total: t});
}
});

React.renderComponent(<Hello />, document.getElementById('content'));

Obviously you could create separate handleChange functions to handle each different input, but that's not very nice. Similarly you could create a component just for an individual input, but I wanted to see if there's a way to do it like this.

vikash chander
  • 49
  • 1
  • 10
T3db0t
  • 3,491
  • 4
  • 28
  • 45

13 Answers13

180

I suggest sticking to standard HTML attributes like name on input Elements to identify your inputs. Also, you don't need to keep "total" as a separate value in state because it is composable by adding other values in your state:

var Hello = React.createClass({
    getInitialState: function() {
        return {input1: 0, input2: 0};
    },
    render: function() {
        const total = this.state.input1 + this.state.input2;

        return (
            <div>{total}<br/>
                <input type="text" value={this.state.input1} name="input1" onChange={this.handleChange} />
                <input type="text" value={this.state.input2} name="input2" onChange={this.handleChange} />
            </div>
        );
    },
    handleChange: function(e) {
        this.setState({[e.target.name]: e.target.value});
    }
});

React.renderComponent(<Hello />, document.getElementById('content'));
Ross Allen
  • 43,772
  • 14
  • 97
  • 95
  • 4
    I was trying to do this initially, but setState({e.target.name... does not work—it's a syntax error. You have to make a temp object with obj[e.target.name] and setState to that. This sort of works, but there seems to be some kind of delay in the state object updating. – T3db0t Jan 11 '14 at 03:47
  • 1
    The "kind of delay" is setState updating during requestAnimationFrame (I assume), so ignore that remark – T3db0t Jan 13 '14 at 21:06
  • @VirgilDisgr4ce can you check this question please?http://stackoverflow.com/questions/21352587/react-requestanimationframe – Sebastien Lorber Jan 25 '14 at 15:42
  • Thanks, @VirgilDisgr4ce. I updated the answer to correctly set the key on the `nextState` object. – Ross Allen Oct 04 '14 at 17:49
  • 25
    @T3db0t This ES6 syntax works: `this.setState({ [e.target.name]: e.target.value });` – XåpplI'-I0llwlg'I - Oct 28 '15 at 07:17
  • 2
    Using bind is the better approach. This approach doesn't work if you attach the onChange handler to an element without a name property, such as a div. – ericgrosse Aug 11 '16 at 06:58
  • 1
    @ericgrosse Agreed I wouldn't suggest this for all event listeners, but form elements all fire `onchange` and have `name` attributes. If you build a plain old HTML form without JavaScript, `name` identifies the elements of the form when it's submitted. I suggest sticking to standard form functionality. – Ross Allen Aug 11 '16 at 14:58
  • 3
    @ericgrosse bind is not better in this case as you're creating a lot of unnecessary functions. also, you could use a data- attribute or something on a different sort of element if need be – aw04 Oct 10 '16 at 19:27
  • If you're using `name` for something else, you can use `data` attributes instead. For example, `data-input-name="input1"` and access it with `e.target.dataset.inputName` – LaurelT Dec 23 '16 at 00:12
  • But what if you generate your fields dynamicly? So you will have multiple fields, how can I give each field a new unique name? So I am mapping over an component that holds my two input fields. I can have a maximum of 3 (x2) input fields, how do I make sure I can get the value of each? – Deelux Dec 23 '16 at 00:20
  • thanks for this. much easy way to do. I tried same but it was Syntax error, then saw your answer, it was just to declare as object. – Gaurravs Nov 10 '18 at 10:16
  • One caveat of this solution is that it only works for dispatchers that supply an event parameter such as this that contains the relevant information. So in this sense, it's API-specific. Nonetheless, I'll mark it as the correct answer since it's in the context of a regular browser form API. – T3db0t Nov 15 '19 at 18:36
107

You can use the .bind method to pre-build the parameters to the handleChange method. It would be something like:

  var Hello = React.createClass({
    getInitialState: function() {
        return {input1:0, 
                input2:0};
    },
    render: function() {
      var total = this.state.input1 + this.state.input2;
      return (
        <div>{total}<br/>
          <input type="text" value={this.state.input1} 
                             onChange={this.handleChange.bind(this, 'input1')} />
          <input type="text" value={this.state.input2} 
                             onChange={this.handleChange.bind(this, 'input2')} />
        </div>
      );
    },
    handleChange: function (name, e) {
      var change = {};
      change[name] = e.target.value;
      this.setState(change);
    }
  });

  React.renderComponent(<Hello />, document.getElementById('content'));

(I also made total be computed at render time, as it is the recommended thing to do.)

fiatjaf
  • 11,479
  • 5
  • 56
  • 72
  • 13
    Just a quick reminder that `this.handleChange.bind(...)` creates a new function. In case you're using `shouldComponentUpdate` with `PureRenderMixin` or something similar it will break. All of your input components will be rerendered when a single value changes. – zemirco Jun 01 '15 at 11:43
  • 5
    If you change implementation of `handleChange` to `handleChange(name) { return event => this.setState({name: event.target.value}); }` you can pass "same" function (won't create new function as it's done by using `.bind()` Then, you can pass such function: `this.handleChange("input1")` – Andreyco Feb 10 '16 at 23:41
  • 1
    I think it is also important to mention, that the nature of setState, is that one will not be able to retrieve the current state in the handChange function, but the previous state using, this.state.input1, for instance. A proper approach will be to create a callback such as `this.setState(change, function () { console.log(this.state.input1); );` reference: http://stackoverflow.com/questions/30782948/why-calling-react-setstate-method-doesnt-mutate-the-state-immediately – Charlie-Greenman Apr 04 '16 at 05:00
  • Hi @flatjaf its a wrong approach as it loses the function on render – Aashiq Sep 26 '20 at 05:02
  • This answer is outdated. Someone please edit it. I don't use React anymore so I'm not sure what would be a proper answer now. – fiatjaf Sep 26 '20 at 20:37
41

The onChange event bubbles... So you can do something like this:

// A sample form
render () {
  <form onChange={setField}>
    <input name="input1" />
    <input name="input2" />
  </form>
}

And your setField method might look like this (assuming you're using ES2015 or later:

setField (e) {
  this.setState({[e.target.name]: e.target.value})
}

I use something similar to this in several apps, and it's pretty handy.

Christopher Davies
  • 4,461
  • 2
  • 34
  • 33
12

Deprecated solution

valueLink/checkedLink are deprecated from core React, because it is confusing some users. This answer won't work if you use a recent version of React. But if you like it, you can easily emulate it by creating your own Input component

Old answer content:

What you want to achieve can be much more easily achieved using the 2-way data binding helpers of React.

var Hello = React.createClass({
    mixins: [React.addons.LinkedStateMixin],
    getInitialState: function() {
        return {input1: 0, input2: 0};
    },

    render: function() {
        var total = this.state.input1 + this.state.input2;
        return (
            <div>{total}<br/>
                <input type="text" valueLink={this.linkState('input1')} />;
                <input type="text" valueLink={this.linkState('input2')} />;
            </div>
        );
    }

});

React.renderComponent(<Hello />, document.getElementById('content'));

Easy right?

http://facebook.github.io/react/docs/two-way-binding-helpers.html

You can even implement your own mixin

Community
  • 1
  • 1
Sebastien Lorber
  • 89,644
  • 67
  • 288
  • 419
7

You can also do it like this:

...
constructor() {
    super();
    this.state = { input1: 0, input2: 0 };
    this.handleChange = this.handleChange.bind(this);
}

handleChange(input, value) {
    this.setState({
        [input]: value
    })
}

render() {
    const total = this.state.input1 + this.state.input2;
    return (
        <div>
            {total}<br />
            <input type="text" onChange={e => this.handleChange('input1', e.target.value)} />
            <input type="text" onChange={e => this.handleChange('input2', e.target.value)} />
        </div>
    )
}
inostia
  • 7,777
  • 3
  • 30
  • 33
  • didn't worked for me. saw React developer tools. its creating and assigning values to variable/object called input, instead of input1 / input2 – Gaurravs Nov 10 '18 at 10:21
  • 1
    @Gaurravs yes you're right. I needed to add `[]` around `input` into setState. That makes the property dynamicaly assigned – inostia Nov 12 '18 at 18:54
7

If anyone looking for Functional Component Example,

import React, { useState } from "react";

export default function Exapmle() {

const [userState, setUserState] = useState({
  firstName: "",
  lastName: ""
})

const handleChange = (e)=>{
   const value = e.target.value;
   setUserState({
    ...userState,
    [e.target.name]: value
   });
}

return (
  <form>

    <label>
      First name
      <input type="text" name="firstName" value={userState.firstName}
        onChange={handleChange}
      />
    </label>

    <label>
      Last name
      <input type="text" name="lastName" value={userState.lastName}
        onChange={handleChange}
      />
    </label>

  </form>
 );
}
Manu
  • 711
  • 8
  • 27
4

You can use a special React attribute called ref and then match the real DOM nodes in the onChange event using React's getDOMNode() function:

handleClick: function(event) {
  if (event.target === this.refs.prev.getDOMNode()) {
    ...
  }
}

render: function() {
  ...
  <button ref="prev" onClick={this.handleClick}>Previous question</button>
  <button ref="next" onClick={this.handleClick}>Next question</button>
  ...
}
XåpplI'-I0llwlg'I -
  • 21,649
  • 28
  • 102
  • 151
0

@Vigril Disgr4ce

When it comes to multi field forms, it makes sense to use React's key feature: components.

In my projects, I create TextField components, that take a value prop at minimum, and it takes care of handling common behaviors of an input text field. This way you don't have to worry about keeping track of field names when updating the value state.

[...]

handleChange: function(event) {
  this.setState({value: event.target.value});
},
render: function() {
  var value = this.state.value;
  return <input type="text" value={value} onChange={this.handleChange} />;
}

[...]
manu3569
  • 183
  • 2
  • 3
0

You can track the value of each child input by creating a separate InputField component that manages the value of a single input. For example the InputField could be:

var InputField = React.createClass({
  getInitialState: function () {
    return({text: this.props.text})
  },
  onChangeHandler: function (event) {
     this.setState({text: event.target.value})
  }, 
  render: function () {
    return (<input onChange={this.onChangeHandler} value={this.state.text} />)
  }
})

Now the value of each input can be tracked within a separate instance of this InputField component without creating separate values in the parent's state to monitor each child component.

Bart Riordan
  • 436
  • 3
  • 8
  • 23
Javasamurai
  • 666
  • 7
  • 21
0

I will provide really simple solution to the problem. Suppose we have two inputs username and password,but we want our handle to be easy and generic ,so we can reuse it and don't write boilerplate code.

I.Our form:

                <form>
                    <input type="text" name = "username" onChange={this.onChange} value={this.state.username}/>
                    <input type="text" name = "password" onChange={this.onChange} value={this.state.password}/>
                    <br></br>
                    <button type="submit">Submit</button>
                </form>

II.Our constructor ,which we want to save our username and password ,so we can access them easily:

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

    this.onSubmit = this.onSubmit.bind(this);
    this.onChange = this.onChange.bind(this);
}

III.The interesting and "generic" handle with only one onChange event is based on this:

onChange(event) {
    let inputName = event.target.name;
    let value = event.target.value;

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


    event.preventDefault();
}

Let me explain:

1.When a change is detected the onChange(event) is called

2.Then we get the name parameter of the field and its value:

let inputName = event.target.name; ex: username

let value = event.target.value; ex: itsgosho

3.Based on the name parameter we get our value from the state in the constructor and update it with the value:

this.state['username'] = 'itsgosho'

4.The key to note here is that the name of the field must match with our parameter in the state

Hope I helped someone somehow :)

Georgi Peev
  • 912
  • 1
  • 14
  • 25
0

The key of your state should be the same as the name of your input field. Then you can do this in the handleEvent method;

this.setState({
        [event.target.name]: event.target.value
});
Rajan
  • 1,501
  • 4
  • 21
  • 37
-1

Hi have improved ssorallen answer. You don't need to bind function because you can access to the input without it.

var Hello = React.createClass({
    render: function() {
        var total = this.state.input1 + this.state.input2;
        return (
             <div>{total}<br/>
                  <input type="text" 
                    value={this.state.input1}
                    id="input1"  
                    onChange={this.handleChange} />
                 <input type="text" 
                    value={this.state.input2}
                    id="input2" 
                    onChange={this.handleChange} />
            </div>
       );
   },
   handleChange: function (name, value) {
       var change = {};
       change[name] = value;
       this.setState(change);
   }
});

React.renderComponent(<Hello />, document.getElementById('content'));
Albert Olivé Corbella
  • 4,061
  • 7
  • 48
  • 66
-1

state object

const [values, setValues] = useState({
    email: "",
    password: "",
  });

function that change state

const handleChange = name => event => {
    setValues({ ...values, [name]: event.target.value });
 };

call above function on onchange of input with "name"

<input 
onChange={handleChange("email")} 
value={email}
className="form-control"
type="email"
       />
Shadab Ali
  • 369
  • 3
  • 10