Matthew Moran
Matthew Moran

Reputation: 1497

How to reset the state of a React component to a previous applied state?

I have a button that toggles a drop down menu. Within this menu a user can toggle check-boxes, apply the changes or cancel.

Cancel should reset the check-boxes to the previous applied changes.

I am struggling to reset the state of the FilterMenu component. I attempted to stringify the initial state, however this has proven to be limited.

How can reset the state of the FilterMenu component to the previous applied changes?

When the user cancels:

Filter Component

//close menu and reset state
handleCancel() {    
    this.setState({menuOpen: false});
    this.props.resetState();
}

resetState is called which parses my initial state and sets it.

Main Component

resetState() {
   this.setState(JSON.parse(this.baseState));
}

Snippet

const FilterMenu = ({checkBoxes, handleChange, handleCancel, handleSave}) => {
  return (
    <div>
      {Object.keys(checkBoxes).map(key => {
        return (
          <div key={key}>
            <label htmlFor={key}>{checkBoxes[key].name}</label>
            <input
              id={key}
              type="checkbox"
              checked={checkBoxes[key].isChecked}
              onChange={handleChange}
            />
          </div>
        );
      })}
      <button onClick={handleCancel}>Cancel</button>
      <button onClick={handleSave}>Save</button>
    </div>
  );
};

class Filter extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      menoOpen: false,
    };

    this.handleCancel = this.handleCancel.bind(this);
    this.handleButtonClick = this.handleButtonClick.bind(this);
  }

  handleCancel() {
    this.setState({menuOpen: false});
    this.props.resetState();
  }

  handleButtonClick() {
    this.setState({menuOpen: !this.state.menuOpen});
  }

  render() {
    return (
      <div>
        <button onClick={this.handleButtonClick}>Choose Fruits</button>
        {this.state.menuOpen && (
          <FilterMenu
            checkBoxes={this.props.checkBoxes}
            handleSave={this.props.handleSave}
            handleCancel={this.handleCancel}
            handleChange={this.props.handleChange}
          />
        )}
      </div>
    );
  }
}

//Parent component
class Main extends React.Component {
  constructor() {
    super();
    this.state = {
      checkBoxes: {
        1: {
          name: 'Apple',
          isChecked: true,
        },
        2: {
          name: 'Pear',
          isChecked: true,
        },
        3: {
          name: 'Tomato',
          isChecked: true,
        },
      },
      fruit: {
        1: {
          name: 'Apple',
        },

        3: {
          name: 'Apple',
        },
        4: {
          name: 'Pear',
        },
        5: {
          name: 'Tomato',
        },
        6: {
          name: 'Apple',
        },
      },

      checkedBoxes: [],
    };

    this.baseState = JSON.stringify(this.state);
    this.fruitFilter = this.fruitFilter.bind(this);
    this.handleSave = this.handleSave.bind(this);
    this.handleChange = this.handleChange.bind(this);
    this.resetState = this.resetState.bind(this);
  }

  resetState() {
    this.setState(JSON.parse(this.baseState));
  }

  //populates the checkedboxs array with name to filter by
  handleSave() {
    //look at the checked boxes
    //filter the fruit based on the ones that are checked`
    const checkedBoxes = Object.keys(this.state.checkBoxes)
      .filter(key => {
        //return name of fruit if it is checked
        return this.state.checkBoxes[key].isChecked
          ? this.state.checkBoxes[key].name
          : false;
      })
      .reduce((a, b) => {
        a.push(this.state.checkBoxes[b].name);
        return a;
      }, []);

    console.log(checkedBoxes);
    this.setState({checkedBoxes: checkedBoxes});
  }

  //handles the checkbox toggle
  handleChange(e) {
    const checkBoxes = {...this.state.checkBoxes};
    checkBoxes[e.target.id].isChecked = e.target.checked;
    this.setState({checkBoxes: checkBoxes});
  }

  //filteres the fruit - if nothing is checked return them all
  fruitFilter(fruit) {
    return Object.keys(fruit)
      .filter(key => {
        return (
          this.state.checkedBoxes.length <= 0 ||
          this.state.checkedBoxes.indexOf(fruit[key].name) > -1
        );
      })
      .reduce((a, b) => {
        a[b] = fruit[b];
        return a;
      }, {});
  }

  render() {
    const visibleFruits = this.fruitFilter(this.state.fruit);
    return (
      <div>
        <Filter
          resetState={this.resetState}
          checkBoxes={this.state.checkBoxes}
          handleSave={this.handleSave}
          handleChange={this.handleChange}
        />
        <div>
          <h2>Filtered Fruit</h2>
          {Object.keys(visibleFruits).map(key => {
            return (
              <div key={key}>
                <span>{visibleFruits[key].name}</span>
              </div>
            );
          })}
        </div>
      </div>
    );
  }
}

ReactDOM.render(<Main />, document.getElementById('react'))
<div id="react"></div>

<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>

Upvotes: 0

Views: 3496

Answers (1)

Moti Korets
Moti Korets

Reputation: 3748

The problem is you are not saving the previous state before modifying it. Remember the constructor is only called once.
The code i have added in handleSave

this.baseState =  JSON.stringify(this.state.checkBoxes)

and in resetState

this.setState({checkBoxes: JSON.parse(this.baseState)});

Here is the modified version:

const FilterMenu = ({checkBoxes, handleChange, handleCancel, handleSave}) => {
  return (
    <div>
      {Object.keys(checkBoxes).map(key => {
        return (
          <div key={key}>
            <label htmlFor={key}>{checkBoxes[key].name}</label>
            <input
              id={key}
              type="checkbox"
              checked={checkBoxes[key].isChecked}
              onChange={handleChange}
            />
          </div>
        );
      })}
      <button onClick={handleCancel}>Cancel</button>
      <button onClick={handleSave}>Save</button>
    </div>
  );
};

class Filter extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      menoOpen: false,
    };

    this.handleCancel = this.handleCancel.bind(this);
    this.handleButtonClick = this.handleButtonClick.bind(this);
  }

  handleCancel() {
    // this.setState({menuOpen: false});
    this.props.resetState();
  }

  handleButtonClick() {
    this.setState({menuOpen: !this.state.menuOpen});
  }

  render() {
    return (
      <div>
        <button onClick={this.handleButtonClick}>Choose Fruits</button>
        {this.state.menuOpen && (
          <FilterMenu
            checkBoxes={this.props.checkBoxes}
            handleSave={this.props.handleSave}
            handleCancel={this.handleCancel}
            handleChange={this.props.handleChange}
          />
        )}
      </div>
    );
  }
}

//Parent component
class Main extends React.Component {
  constructor() {
    super();
    this.state = {
      checkBoxes: {
        1: {
          name: 'Apple',
          isChecked: true,
        },
        2: {
          name: 'Pear',
          isChecked: true,
        },
        3: {
          name: 'Tomato',
          isChecked: true,
        },
      },
      fruit: {
        1: {
          name: 'Apple',
        },

        3: {
          name: 'Apple',
        },
        4: {
          name: 'Pear',
        },
        5: {
          name: 'Tomato',
        },
        6: {
          name: 'Apple',
        },
      },

      checkedBoxes: ['Apple', 'Pear', 'Tomato'],
    };
    this.baseState =  JSON.stringify(this.state.checkBoxes)
    this.fruitFilter = this.fruitFilter.bind(this);
    this.handleSave = this.handleSave.bind(this);
    this.handleChange = this.handleChange.bind(this);
    this.resetState = this.resetState.bind(this);
  }

  resetState() {
    console.log(this.baseState)
    this.setState({checkBoxes: JSON.parse(this.baseState)});
  }

  //populates the checkedboxs array with name to filter by
  handleSave() {
    //look at the checked boxes
    //filter the fruit based on the ones that are checked`
    this.baseState =  JSON.stringify(this.state.checkBoxes)
    const checkedBoxes = Object.keys(this.state.checkBoxes)
      .filter(key => {
        //return name of fruit if it is checked
        return this.state.checkBoxes[key].isChecked
          ? this.state.checkBoxes[key].name
          : false;
      })
      .reduce((a, b) => {
        a.push(this.state.checkBoxes[b].name);
        return a;
      }, []);
    console.log(checkedBoxes);
    this.setState({checkedBoxes: checkedBoxes});
  }

  //handles the checkbox toggle
  handleChange(e) {
    const checkBoxes = {...this.state.checkBoxes};
    checkBoxes[e.target.id].isChecked = e.target.checked;
    
    this.setState({checkBoxes: checkBoxes});
  }

  //filteres the fruit - if nothing is checked return them all
  fruitFilter(fruit) {
    return Object.keys(fruit)
      .filter(key => {
        return (
          this.state.checkedBoxes.length <= 0 ||
          this.state.checkedBoxes.indexOf(fruit[key].name) > -1
        );
      })
      .reduce((a, b) => {
        a[b] = fruit[b];
        return a;
      }, {});
  }

  render() {
    const visibleFruits = this.fruitFilter(this.state.fruit);
    return (
      <div>
        <Filter
          resetState={this.resetState}
          checkBoxes={this.state.checkBoxes}
          handleSave={this.handleSave}
          handleChange={this.handleChange}
        />
        <div>
          <h2>Filtered Fruit</h2>
          {Object.keys(visibleFruits).map(key => {
            return (
              <div key={key}>
                <span>{visibleFruits[key].name}</span>
              </div>
            );
          })}
        </div>
      </div>
    );
  }
}

ReactDOM.render(<Main />, document.getElementById('react'))
<div id="react"></div>

<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>

Please note that using JSON.stringify to clone objects is discouraged due to performance issues and for a real world application you should use proper deepCloning method (either from a library or your own implementation)

Upvotes: 0

Related Questions