Reputation: 1497
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));
}
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
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