Monday, May 20, 2024
 Popular · Latest · Hot · Upcoming
42
rated 0 times [  44] [ 2]  / answers: 1 / hits: 21348  / 6 Years ago, tue, july 10, 2018, 12:00:00

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.


More From » reactjs

 Answers
-2

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 :)


[#54017] Thursday, July 5, 2018, 6 Years  [reply] [flag answer]
Only authorized users can answer the question. Please sign in first, or register a free account.
freddiem

Total Points: 456
Total Questions: 116
Total Answers: 101

Location: Dominica
Member since Mon, Jan 4, 2021
3 Years ago
;