In my React app I am trying to catch some event when checkbox is clicked in order to proceed some state filtering, and show only items are needed.
event
is coming from child's checkbox with some name
. There is 3 checkboxes, so I need to know the name
which one is clicked.
some of them
<input
type="checkbox"
name="name1"
onClick={filterHandler}
/>Something</div>
state is something like
state = {
items = [
{
name: "name1",
useful: true
},{
name: "name2",
useful: false
}],
filteredItems: []
}
here is the handler
filterHandler = (evt) => {
let checked = evt.target.checked;
let name = evt.target.name;
let filteredItems = this.state.items.filter(item => {
return item[name] === checked; // filtered [{...},{...}...]
});
// when filtered - push to state and show all from here in <ItemsList />
this.setState({ filteredItems })
}
ItemsList component for "showing" is like so:
<ItemsList
items={this.state.filteredItems.length === 0 ? this.state.items : this.state.filteredItems}
/>
When checkbox is one and only - its working fine. But I have three of those boxes -- complications appears:
1) when checking next box I operate with original un-filtered items array - so for this purpose I need already filtered array.
2) I cant use my filteredItems
array reviously filtered, because when unchecking box that array gets empty.To have 3rd "temporary" array seems a little weird.
I tried this way, pretty similar also
this.setState({
filteredItems: this.state.items.filter(item => {
if (item[name] === checked) {
console.log('catch');
return Object.assign({}, item)
} else {
console.log('no hits')
}
})
and this is almost good, but when uncheck filteredItems
are filled with opposite values ((
I feel there is a better approach, please suggest.
Controlling the input checkbox As mentioned earlier, React recommends making our form elements a controlled field. To do this, we must add a component state to manage the user's input and then pass the state variable to the input. For checkbox input, we will assign the state to the input checked attribute.
The state can be changed by providing the new state value or an updater function. The updater function takes the current state and returns the new state. The state related to a checkbox input is a boolean. When the state is false the checkbox is unchecked, when the state is true the checkbox is checked.
A checkbox input can only have two states in a form: checked or unchecked. It either submits its value or doesn't. Visually, there are three states a checkbox can be in: checked, unchecked, or indeterminate. ⚠️ When indeterminate is set, the value of the checked prop only impacts the form submitted values.
You can do it by storing the checked state of the filters.
For example, your state can look something like:
state = {
items: [
{
name: "name1",
useful: true
},
{
name: "name2",
useful: false
}
],
filters: { 'name1': false, 'name2': false}, // key value pair name:checked
filteredItems: []
};
Then your click/change handler would update both the filtered list and the actual filters state (what's checked).
Here's an example of that:
(Update: Heavily commented as per request in comments)
// Using syntax: someFunc = (params) => { ... }
// To avoid having to bind(this) in constructor
onChange = evt => {
// const is like var and let but doesn't change
// We need to capture anything dependent on
// evt.target in synchronous code, and
// and setState below is asynchronous
const name = evt.target.name;
const checked = evt.target.checked;
// Passing function instead of object to setState
// This is the recommended way if
// new state depends on existing state
this.setState(prevState => {
// We create a new object for filters
const filters = {
// We add all existing filters
// This adds them with their existing values
...prevState.filters,
// This is like:
// filters[name] = checked
// which just overrides the value of
// the prop that has the name of checkbox
[name]: checked
};
// Object.keys() will return ["name1", "name2"]
// But make sure that "filters" in
// our initial state has all values
const activeFilterNames = Object.keys(filters).filter(
// We then filter this list to only the ones that
// have their value set to true
// (meaning: checked)
// We set this in the `const filter =` part above
filterName => filters[filterName]
);
// We get the full list of items
// (Make sure it's set in initial state)
// Then we filter it to match only checked
const filteredItems = prevState.items.filter(item =>
// For each item, we loop over
// all checked filters
// some() means: return true if any of the
// array elements in `activeFilterNames`
// matches the condition
activeFilterNames.some(
// The condition is simply the filter name is
// the same as the item name
activeFilterName => activeFilterName === item.name
)
);
// The object returned from setState function
// is what we want to change in the state
return {
// this is the same as
// { filter: filters,
// filteredItems: filteredItems }
// Just taking advantage of how const names
// are the same as prop names
filters,
filteredItems
};
});
};
I'm using latest features of JS / Babel in here but hopefully the code is clear. I also had to use evt.target
before entering setState()
Here's a full component example:
import * as React from "react";
import ReactDOM from "react-dom";
import "./styles.css";
class App extends React.Component {
state = {
items: [
{
name: "name1",
useful: true
},
{
name: "name2",
useful: false
}
],
filters: { name1: false, name2: false },
filteredItems: []
};
// Using syntax: someFunc = (params) => { ... }
// To avoid having to bind(this) in constructor
onChange = evt => {
// const is like var and let but doesn't change
// We need to capture anything dependent on
// evt.target in synchronous code, and
// and setState below is asynchronous
const name = evt.target.name;
const checked = evt.target.checked;
// Passing function instead of object to setState
// This is the recommended way if
// new state depends on existing state
this.setState(prevState => {
// We create a new object for filters
const filters = {
// We add all existing filters
// This adds them with their existing values
...prevState.filters,
// This is like:
// filters[name] = checked
// which just overrides the value of
// the prop that has the name of checkbox
[name]: checked
};
// Object.keys() will return ["name1", "name2"]
// But make sure that "filters" in
// our initial state has all values
const activeFilterNames = Object.keys(filters).filter(
// We then filter this list to only the ones that
// have their value set to true
// (meaning: checked)
// We set this in the `const filter =` part above
filterName => filters[filterName]
);
// We get the full list of items
// (Make sure it's set in initial state)
// Then we filter it to match only checked
const filteredItems = prevState.items.filter(item =>
// For each item, we loop over
// all checked filters
// some() means: return true if any of the
// array elements in `activeFilterNames`
// matches the condition
activeFilterNames.some(
// The condition is simply the filter name is
// the same as the item name
activeFilterName => activeFilterName === item.name
)
);
// The object returned from setState function
// is what we want to change in the state
return {
// this is the same as
// { filter: filters,
// filteredItems: filteredItems }
// Just taking advantage of how const names
// are the same as prop names
filters,
filteredItems
};
});
};
renderCheckboxes() {
return Object.keys(this.state.filters).map((name, index) => {
return (
<label key={index}>
<input
onChange={this.onChange}
type="checkbox"
checked={this.state.filters[name]}
name={name}
/>
{name}
</label>
);
});
}
render() {
const items = this.state.filteredItems.length
? this.state.filteredItems
: this.state.items;
return (
<div>
<div>{this.renderCheckboxes()}</div>
<ul>
{items.map(item => (
<li key={item.name}>
{item.name}
{item.useful && " (useful)"}
</li>
))}
</ul>
</div>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
You can try it live from here:
https://codesandbox.io/embed/6z8754nq1n
You can of course create different variations of this as you wish. For example you might choose to move the filtering to the render function instead of the change event, or store how you store the selected filters, etc, or just use it as is. Whatever suits you best :)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With