简体   繁体   中英

Toggling nested state object in React

I have a state object that contains an array of objects:

this.state = {
  feeling: [
    { name: 'alert', status: false },
    { name: 'calm', status: false },
    { name: 'creative', status: false },
    { name: 'productive', status: false },
    { name: 'relaxed', status: false },
    { name: 'sleepy', status: false },
    { name: 'uplifted', status: false }
  ]
}

I want to toggle the boolean status from true to false on click event. I built this function as a click handler but it doesn't connect the event into the state change:

buttonToggle = (event) => {
  event.persist();
  const value = !event.target.value

  this.setState( prevState => ({
    status: !prevState.status
  }))
}

I'm having a hard time following the control flow of the nested React state change, and how the active event makes the jump from the handler to the state object and vice versa.

The whole component:

export default class StatePractice extends React.Component {

  constructor() {
    super();
    this.state = {
      feeling: [
        { name: 'alert', status: false },
        { name: 'calm', status: false },
        { name: 'creative', status: false },
        { name: 'productive', status: false },
        { name: 'relaxed', status: false },
        { name: 'sleepy', status: false },
        { name: 'uplifted', status: false }
      ]
    }
  }

  buttonToggle = (event) => {
    event.persist();
    const value = !event.target.value

    this.setState( prevState => ({
      status: !prevState.status
    }))
  }



  render() {
    return (  
      <div>
        { this.state.feeling.map(
            (stateObj, index) => { 
              return <button 
                key={ index }
                onClick={ this.buttonToggle } 
                value={ stateObj.status } >
                  { stateObj.status.toString() }
                </button>
            }
          )
        }
      </div>
    )
  }
}  

In order to solve your problem, you should first send the index of the element that is going to be modified to your toggle function :

onClick = {this.buttonToggle(index)} 

Then tweak the function to receive both the index and the event.

Now, to modify your state array, copy it, change the value you are looking for, and put it back in your state :

buttonToggle = index => event => {
    event.persist();
    const feeling = [...this.state.feeling]; //Copy your array
    feeling[index] = !feeling[index];
    this.setState({ feeling }); 
}

You can also use slice to copy your array, or even directly send a mapped array where only one value is changed.

Updating a nested object in a react state object is tricky. You have to get the entire object from the state in a temporary variable, update the value within that variable and then replace the state with the updated variable. To do that, your buttonToggle function needs to know which button was pressed.

return <button 
  key={ index }
  onClick={ (event) => this.buttonToggle(event, stateObj.name) } 
  value={ stateObj.status } >
    { stateObj.status.toString() }
  </button>

And your buttonToggle function could look like this

buttonToggle = (event, name) => {
  event.persist();

  let { feeling } = this.state;
    let newFeeling = [];

  for (let index in feeling) {
    let feel = feeling[index];
    if (feel.name == name) {
        feel = {name: feel.name, status: !feel.status};
    }
    newFeeling.push(feel);
  }

  this.setState({
    feeling: newFeeling,
  });
}

Here's a working JSFiddle.

Alternatively, if you don't need to store any more data per feeling than "name" and "status", you could rewrite your component state like this:

 feeling: {
    alert: false,
    calm: false,
    creative: false,
    etc...
  }

And buttonToggle:

 buttonToggle = (event, name) => {
   event.persist();
   let { feeling } = this.state;
   feeling[name] = !feeling[name];
   this.setState({
    feeling
   });
 }

I think you need to update the whole array when get the event. And it is better to not mutate the existing state. I would recommend the following code

export default class StatePractice extends React.Component {
  constructor() {
    super();
    this.state = {
      feeling: [
        { name: "alert", status: false },
        { name: "calm", status: false },
        { name: "creative", status: false },
        { name: "productive", status: false },
        { name: "relaxed", status: false },
        { name: "sleepy", status: false },
        { name: "uplifted", status: false },
      ],
    };
  }

  buttonToggle = (index, value) => (event) => {
    event.persist();
    const toUpdate = { ...this.state.feeling[index], status: !value };
    const feeling = [...this.state.feeling];
    feeling.splice(index, 1, toUpdate);
    this.setState({
      feeling,
    });
  };

  render() {
    return (
      <div>
        {this.state.feeling.map((stateObj, index) => {
          return (
            <button
              key={index}
              onClick={this.buttonToggle(index, stateObj.status)}
              value={stateObj.status}
            >
              {stateObj.status.toString()}
            </button>
          );
        })}
      </div>
    );
  }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM