I am new to React and trying to build a TODO app. What I'm trying to achieve is: 1) after each checkbox's click, I would like to show its associated data on top of what is already displayed rather than simultaneously (i.e checkbox 2 displaying its data instead of replacing another checkbox's data).
I guess it has something to do with the way I'm storing and filtering my items but I'm struggling to find the way out.
Also, and for it to be perfect, 2) I'd like to remove the behaviour that checking/unchecking my checkboxes is performing the same action (i.e when all checkboxes are clicked, unticking one will call again its associated function rather than doing nothing and keep its data displayed on the screen) and 3) allowing the fact that some checkboxes cannot be both ticked at the same time (the Clear and Show all checkboxes for example).
Thanks in advance
CODE
import React from "react";
import ReactDOM from "react-dom";
import Items from "./Items.js"
class Checkbox extends React.Component {
constructor(props) {
super(props);
this.state = {
Data: Items,
storeData: Items,
}
}
myFunction = (category) => {
const filterData = this.state.Data.filter(elem => elem.category === category);
this.setState({storeData: filterData});
}
myFunctionAll = () => {
this.setState({storeData: Items});
}
render() {
console.log(this.state.storeData)
console.log(this.state.Data)
return(
<div>
<label>
Filter A
<input type="checkbox" onClick={() => this.myFunction("A")} />
</label>
<label>
Filter B
<input type="checkbox" onClick={() => this.myFunction("B")} />
</label>
<label>
Filter C
<input type="checkbox" onClick={() => this.myFunction("C")} />
</label>
<label>
Clear All
<input type="checkbox" onClick={() => this.myFunction(null)} />
</label>
<label>
Show All
<input type="checkbox" onClick={() => this.myFunctionAll()} />
</label>
<br />
{this.state.storeData.map(elem => (
<li key={elem.id}>
Id: {elem.id} Category: {elem.category}
</li>))}
</div>
)
}
}
ReactDOM.render(<Checkbox />, document.getElementById("root"))
DATA
const Items = [
{
"id": 1,
"name": "First item",
"category": "A"
},
{
"id": 2,
"name": "Second item",
"category": "B"
},
{
"id": 3,
"name": "Third item",
"category": "C"
},
{
"id": 4,
"name": "Fourth item",
"category": "A"
},
]
export default Items
I think the main issue with your code is trying to filter and save back into state your data. I think it is better to instead keep your data intact, i.e. don't mutate it, and instead store in state what "filters" are active. You can simply filter your state data array in the render function on-the-fly.
Add a filters
object to your state.
state = {
data: Items,
filters: {}
};
Create a toggleFilter
function to consume an onChange
event. This will toggle what filters are active (i.e. checked) in state.
toggleFilter = (e) => {
const { name, checked } = e.target;
this.setState((prevState) => ({
filters: {
...prevState.filters,
[name]: checked
}
}));
};
Add a name
attribute and onChange
handler to each checkbox input, where X
is the name of each filter, i.e. "A", "B", or "C".
<label>
Filter X
<input
type="checkbox"
name='X'
onChange={this.toggleFilter}
checked={!!this.state.filters.X} // <-- coerce to boolean so value is defined
/>
</label>
Finally, filter and map your data. If some filters object value is truthy then check which filter is active against the current element's category, otherwise just return true to indicate the value should not be filtered from the result array.
{this.state.data
.filter((el) => {
const { filters } = this.state;
if (Object.values(filters).some(Boolean)) { // <-- if some filter is active
if (filters.NONE) return false; // <-- if clear all active then filter out
return filters.ALL || filters[el.category]; // <-- check if ALL or category active
}
return true;
})
.map((elem) => (
<li key={elem.id}>
Id: {elem.id} Category: {elem.category}
</li>
))}
Full Code
const filterOptions = [
{
filter: "A",
label: "Filter A"
},
{
filter: "B",
label: "Filter B"
},
{
filter: "C",
label: "Filter C"
},
{
filter: "NONE",
label: "Clear All"
},
{
filter: "ALL",
label: "Show All"
}
];
class Checkbox extends Component {
state = {
data: Items,
filters: {}
};
toggleFilter = (e) => {
const { name, checked } = e.target;
this.setState((prevState) => ({
filters: {
...prevState.filters,
[name]: checked
}
}));
};
clearFilters = () => this.setState({ filters: {} });
render() {
return (
<div>
{filterOptions.map(({ filter, label }) => (
<label key={filter}>
{label}
<input
type="checkbox"
name={filter}
onChange={this.toggleFilter}
checked={!!this.state.filters[filter]}
/>
</label>
))}
<button type="button" onClick={this.clearFilters}>
Clear Filters
</button>
<br />
{this.state.data
.filter((el) => {
const { filters } = this.state;
if (Object.values(filters).some(Boolean)) {
if (filters.NONE) return false;
return filters.ALL || filters[el.category];
}
return true;
})
.map((elem) => (
<li key={elem.id}>
Id: {elem.id} Category: {elem.category}
</li>
))}
</div>
);
}
}