2

How do I write a single setState method to update the value for multiple input elements when the state object is nested as shown below?

Note: State shouldn't be mutated

When the state is not nested, we could do it like:

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

But how do we do it when the state object is nested?

class FormContainer extends Component {

  constructor () {

  this.state = {
      formControls: {
          email: {
            value: ''
          },
          user: {
            value: ''
          },
          password: {
            value: ''
          }
      }
  }

}

 changeHandler = event => {

  const name = event.target.name;
  const value = event.target.value;

  this.setState({
     //code here


  });
}


  render() {
     return (
      <form>

          <input type="email" 
                 name="email" 
                 value={this.state.formControls.email.value} 
                 onChange={this.changeHandler} 
          />

          <input type="text" 
                 name="user" 
                 value={this.state.formControls.name.value} 
                 onChange={this.changeHandler} 
          />

          <input type="password" 
                 name="password" 
                 value={this.state.formControls.password.value} 
                 onChange={this.changeHandler} 
          />

       </form>      
     );
   }

}
joy08
  • 9,004
  • 8
  • 38
  • 73
Nag
  • 806
  • 1
  • 13
  • 28
  • Does this answer your question? [How to update nested state properties in React](https://stackoverflow.com/questions/43040721/how-to-update-nested-state-properties-in-react) – Murat Karagöz Mar 12 '20 at 09:21

3 Answers3

2

You mixed up email and name, here is a working example:

class Component extends React.Component {
  state = {
    formControls: {
      email: {
        value: '',
      },
      name: {
        value: '',
      },
      password: {
        value: '',
      },
    },
  };

  changeHandler = event => {
    const name = event.target.name;
    const value = event.target.value;

    this.setState({
      ...this.state,//copy state
      formControls: {//set state.formControls
        ...this.state.formControls,//copy sate.formControls
        [name]: {//set state.formControls[name]
          ...this.state.formControls[name],//copy state.formControls[name]
          value,//set state.formControls[name].value
        },
      },
    });
  };

  render() {
    return (
      <form>
        <input
          type="email"
          name="email"
          value={this.state.formControls.email.value}
          onChange={this.changeHandler}
        />

        <input
          type="text"
          name="name"
          value={this.state.formControls.name.value}
          onChange={this.changeHandler}
        />

        <input
          type="password"
          name="password"
          value={this.state.formControls.password.value}
          onChange={this.changeHandler}
        />
      </form>
    );
  }
}

//render app
ReactDOM.render(
  <Component />,
  document.getElementById('root')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>

Setting nested values with spread will get messy very quickly, let's say I want to change month of data.person.dateOfBirth:

const data = {
  person: {
    dateOfBirth: {
      month: 12,
      year: 1999,
      day: 31,
    },
  },
};

I have to copy every nest level when using spread:

const newDataWithDifferentMonth = {
  ...data, //copy data
  person: {
    ...data.person, //copy data.person
    dateOfBirth: {
      ...data.person.dateOfBirth, //copy data.person.dateOfBirth
      month: 11,
    },
  },
};

To help with that you can write a helper that will clean up the code:

const newDataWithDifferentMonth = set(
  data,
  ['person', 'dateOfBirth', 'month'],
  () => 11
);
HMR
  • 37,593
  • 24
  • 91
  • 160
  • can you explain me briefly whats happening here, formControls: { ...this.state.formControls, [name]: value, } – Nag Mar 12 '20 at 09:26
  • do we need ...this.state.formControls? – Nag Mar 12 '20 at 09:27
  • @Nag You descided to put the text values in `state.formControls` so yes, you need to copy nested values. I wrote a [helper](https://gist.github.com/amsterdamharu/659bb39912096e74ba1c8c676948d5d9) to set nested state values. I updated the answer with some comments when setting the state. – HMR Mar 12 '20 at 09:32
  • @Nag If you use the [helper](https://gist.github.com/amsterdamharu/659bb39912096e74ba1c8c676948d5d9) the code would look cleaner: `this.setState(set(this.state, ['formControls',name], () => value))`. Setting a nested value without mutating is messy code. – HMR Mar 12 '20 at 09:35
  • the result is not nested, I'm getting output as, Object { email: "abc@gmail.com", name: "abc", password: "xyz" } – Nag Mar 12 '20 at 09:46
  • @Nag `this.state.formContols` is nested, `formControls` is nested in `this.state` – HMR Mar 12 '20 at 09:47
  • it should've been Object { email: {value: "abc@gmail.com" }, name: {value: "abc"}, password: {value:"xyz"} } – Nag Mar 12 '20 at 09:48
  • @Nag, I see, there is even an extra level of nesting, updated my answer. – HMR Mar 12 '20 at 09:53
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/209512/discussion-between-nag-and-hmr). – Nag Mar 12 '20 at 09:53
0

Use prev state and spread

this.setState((prevState) => ({
  ...prevState,
  [name]: value,
}));
Joe Lloyd
  • 19,471
  • 7
  • 53
  • 81
0

There are multiple ways to change the nested state object.

  1. You can use immutablejs to deepClone.
  2. You can use the legacy Object.assign() which es5 provides.
  3. spead operator help us to shallow copy the object but deep clone doesnot work.
  4. JSON.parse(JSON.stringify(this.state)) and mutate the necessary nested object.

code:

changeHandler = event => {
          const inputName = event.target.name;
          const inputValue = event.target.value;
             let updatedFormState = Object.assign({}, this.state);
             updatedFormState.form[inputName].value = inputValue;
             this.setState(updatedFormState);
        }
CodeZombie
  • 2,027
  • 3
  • 16
  • 30