Filtering a List Using Checkboxes

Filtering list of items with JQuery checkboxes

I would use data attributes instead of a class. This means you can group your checkbox click into one function. See below:

$(document).ready(function () {
$('#filters :checkbox').click(function () { if ($('input:checkbox:checked').length) { $('li').hide(); $('input:checkbox:checked').each(function () { $('li[data-' + $(this).prop('name') + '*="' + $(this).val() + '"]').show(); }); } else { $("li").show(); } }); });
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script><article id="filters">    <section id="price">        <p id="fHeader">Price</p>        <input type="checkbox" name="price" value="p1" id="p1" />£0 - £5        <br/>        <input type="checkbox" name="price" value="p2" id="p2" />£5 - £10        <br/>        <input type="checkbox" name="price" value="p3" id="p3" />£10 - £50        <br/>        <input type="checkbox" name="price" value="p4" id="p4" />£50 - £100        <br/>        <input type="checkbox" name="price" value="p5" id="p5" />£100 - £500        <br/>        <input type="checkbox" name="price" value="p6" id="p6" />£500 - £1000        <br/>        <input type="checkbox" name="price" value="p7" id="p7" />£1000 +        <br/>    </section>    <section id="category">        <p id="fHeader">Category</p>        <input type="checkbox" name="category" value="instruments" id="instruments" />Instruments        <br/>        <input type="checkbox" name="category" value="sheetmusic" id="sheet-music" />Sheet Music        <br/>        <input type="checkbox" name="category" value="accessories" id="accessories" />Accessories        <br/>    </section></article><article id="products">    <ul id="productList">        <li data-price="p5" data-category="instruments">Buffet B12 Clarinet £400</li>        <li data-price="p7" data-category="instruments">Yamaha Clarinet £1700</li>        <li data-price="p6" data-category="instruments">Trevor James Alto Saxophone £700</li>        <li data-price="p3" data-category="instruments">Aulos Recorder £16</li>    </ul></article>

Filter array of objects through checkboxes

I did some refactoring.

  1. Made data more generic.
    export const listCheckboxesRating = {
key: 'rating',
data: [
{
id: 1,
name: 'rating1',
label: 'Rating 1',
value: 1
},
...
}

  1. Refactor Checkbox component and just pass data.
    <Checkboxes
list={listCheckboxesGenre}
handleFilters={handleFilters}
/>

  1. Made filtration process work with value and key.
    // Filtration process
let filteredMovies = data.filter((movie) => {
for (let key in newFilters) {
if (
newFilters.hasOwnProperty(key) &&
Array.isArray(newFilters[key]) &&
newFilters[key].length > 0
) {
if (
newFilters[key].findIndex((value) => movie[key] === value) === -1
) {
return false;
}
}
}
return true;
});

Working DEMO:
https://codesandbox.io/s/react-filter-forked-gs2b2?file=/src/components/App.js

Filter The List Using Multiple CheckBoxes in React Js

The checked state is not valid. We should store it as an array and push/pop the checked/unchecked items from it.

https://codesandbox.io/s/upbeat-ramanujan-b2jui

import React from "react";
import "./Search.css";

class Search extends React.Component {
constructor() {
super();
this.state = {
filterList: [
{
id: 11,
name: "Part Time",
value: "PART_TIME"
},
{
id: 12,
name: "Full Time",
value: "FULL_TIME"
},
{
id: 13,
name: "Freelancer",
value: "FREELANCER"
}
],
searchLists: [
{
id: 1,
type: "PART_TIME",
name: "Akash",
location: "bangalore",
experience: 1
},
{
id: 2,
type: "PART_TIME",
name: "feroz",
location: "mumbai",
experience: 3
},
{
id: 3,
type: "FULL_TIME",
name: "Farheen",
location: "agra",
experience: 5
},
{
id: 4,
type: "FREELANCER",
name: "Raju",
location: "chennai",
experience: 6
},
{
id: 5,
type: "FULL_TIME",
name: "Asif",
location: "vegas",
experience: 7
}
],
activeFilter: []
};
}

onFilterChange(filter) {
const { filterList, activeFilter } = this.state;
if (filter === "ALL") {
if (activeFilter.length === filterList.length) {
this.setState({ activeFilter: [] });
} else {
this.setState({ activeFilter: filterList.map(filter => filter.value) });
}
} else {
if (activeFilter.includes(filter)) {
const filterIndex = activeFilter.indexOf(filter);
const newFilter = [...activeFilter];
newFilter.splice(filterIndex, 1);
this.setState({ activeFilter: newFilter });
} else {
this.setState({ activeFilter: [...activeFilter, filter] });
}
}
}

render() {
const { filterList, activeFilter } = this.state;
let filteredList;
if (
activeFilter.length === 0 ||
activeFilter.length === filterList.length
) {
filteredList = this.state.searchLists;
} else {
filteredList = this.state.searchLists.filter(item =>
this.state.activeFilter.includes(item.type)
);
}
return (
<div className="searchContainer">
<form>
<label htmlFor="myInput">All</label>
<input
id="myInput"
type="checkbox"
onClick={() => this.onFilterChange("ALL")}
checked={activeFilter.length === filterList.length}
/>
{this.state.filterList.map(filter => (
<React.Fragment>
<label htmlFor={filter.id}>{filter.name}</label>
<input
id={filter.id}
type="checkbox"
checked={activeFilter.includes(filter.value)}
onClick={() => this.onFilterChange(filter.value)}
/>
</React.Fragment>
))}
</form>
<ul style={{ marginLeft: "70px" }}>
{filteredList.map(item => (
<div key={item.id}>
<li>
{item.name} -- {item.type}
</li>
</div>
))}
</ul>
</div>
);
}
}

export default Search;

Checkboxes to change state and filter list

So this ended up being more complicated as to why it wouldn't work (had to have a friend help me rewrtie the method) partly due to how I had the db set up that it was pulling associations from (lodash had to get involved).

but regrding the checkboxes in case anyone is looking for checkbox and state help in the future thi sis what worked:

         <div className="doubleCol">
{this.state.symptoms.map(item => (
<ListItem key={item.ObjectID}>
<input
name="slector"
type="checkbox"
className="sympSelect"
onClick={() => this.setState(prevState => ({ selectedSymptom: [...prevState.selectedSymptom, item] }))}

/>
{item.name}
</ListItem>
))}
</div>

as for the rest of the method issues I will update the gist in the first post if anyone is actually interested.

Keep dropdown open when active checkbox

The concept is, you need to check filter data with while opening dialog, To simplify the process I am using ExpansionTile. You can check this demo and customize the behavior and look.

Run on dartPad, Click fab to open dialog and touch outside the dialog to close this.

class ExTExpample extends StatefulWidget {
ExTExpample({Key? key}) : super(key: key);

@override
State<ExTExpample> createState() => _ExTExpampleState();
}

class _ExTExpampleState extends State<ExTExpample> {
// you can use map or model class or both,
List<String> filter_data = [];
List<String> brands = ["Apple", "SamSung"];
List<String> os = ["iOS", "Android"];

_showFilter() async {
await showDialog(
context: context,
builder: (c) {
// you can replace [AlertDialog]
return AlertDialog(
content: StatefulBuilder(
builder: (context, setSBState) => SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
ExpansionTile(
title: const Text("Brand"),

/// check any of its's item is checked or not
initiallyExpanded: () {
// you can do different aproach
for (final f in brands) {
if (filter_data.contains(f)) return true;
}
return false;
}(),
children: [
...brands.map(
(brandName) => CheckboxListTile(
value: filter_data.contains(brandName),
title: Text(brandName),
onChanged: (v) {
if (filter_data.contains(brandName)) {
filter_data.remove(brandName);
} else {
filter_data.add(brandName);
}

setSBState(() {});
//you need to reflect the main ui, also call `setState((){})`
},
),
),
],
),
ExpansionTile(
title: const Text("select OS"),

/// check any of its's item is checked or not
initiallyExpanded: () {
// you can do different aproach
for (final f in os) {
if (filter_data.contains(f)) return true;
}
return false;
}(),
children: [
...os.map(
(osName) => CheckboxListTile(
value: filter_data.contains(osName),
title: Text(osName),
onChanged: (v) {
if (filter_data.contains(osName)) {
filter_data.remove(osName);
} else {
filter_data.add(osName);
}

setSBState(() {});
//you need to reflect the main ui, also call `setState((){})`
},
),
),
],
),
],
),
),
),
);
},
);
}

@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: FloatingActionButton(
onPressed: () {
_showFilter();
},
),
),
);
}
}

How to apply filter in a list with Checkbox in ReactJS?

State Contol

Generally when you need to communicate between components, the solution is Lifting State Up. Meaning that the state should be controlled by a common ancestor.

Instead of having this in your DrawerComponent:

const [activeFilter, setActiveFilter] = React.useState([]);

Move that hook up to your PerfumeStore and pass activeFilter and setActiveFilter as props to DrawerComponent.

You need to add an onChange function to the Checkbox components in DrawerComponent which adds or removes the category by calling setActiveFilter.

So now you need to apply the activeFilter to your list of perfumes, which is determined in Card. You could move all of that filtering up to PerfumeStore, but to keep it simple let's pass activeFilter down as a prop to Card (it just needs to read but not write the filter, so we don't pass down setActiveFilter). Now the Card component has the information that it needs to filter the items based on the selected categories.

Redux Selectors

Everything so far has just had to do with react and the fact that you are using redux hasn't come into play at all. The way that you would incorporate redux principles, if you wanted to, is do define some of the filtering and mapping logic outside of your components as selector functions of state and other arguments. Then instead of calling useSelector to get a huge chunk of state which you process inside the component, you can call useSelector with a selector that gets just the data which you need.

An obvious place to do this is in DrawerComponent, where you are getting the category list. Make a selector function getPerfumeCategories which takes the state and returns the categories. Then in your component, you call const allCategories = useSelector(getPerfumeCategories);

Now all that this component is responsible for is rendering the categories. It is no longer responsible for storing the selections (we've already moved the useState out) or for finding the categories from the state. This is good! You can read up on principles like the Single Responsibility Principle, Separation of Concerns, Logic vs. Presentation components, etc. if you want a deeper understanding of why this is good.

In Card you could use a selector that gets an already-filtered list of perfumes. But in this case a getCurrentPagePerfumes function would take a lot of different arguments so it's kind of messy.

Edit: Filtering

You've asked for help with how to apply the value of activeFilter to filter the perfumes which are shown in your list.

Multiple categories can be selected at once, so activeFilter needs to identify all of the actively selected categories. I first suggested an array of names, but removing items from an array (without mutation) is more complicated than assigning values to objects.

So then I thought about having an object where the keys are the category names and the values are a boolean true/false of whether the category is checked. This makes handleChange really simple because we can update the value for that key to the value of event.target.checked.

  const handleChange = (text) => (event) => {
setActiveFilter((prev) => ({
...prev,
[text]: event.target.checked,
}));
};
  • ...prev says "keep everything the same except the key that I am changing".
  • [text] says "the key I am updating is the variable text, not the literal key 'text'"
  • event.target.checked is the boolean of whether this category is checked.

We could set an initial state for activeFilter which includes a key for every category and all the values are false (ie. nothing selected). Or we could allow for the object to be incomplete with the assumption that if it key isn't included, then it isn't checked. Let's do that.

So now our activeFilter looks something like: {Floral: true, Floriental: false, Fresh: true} where some are true, some are false, and lots are missing and therefore assumed to be false.

We need to figure out how to filter the displayed perfumes based on the value of activeFilter. Let's start by writing a function that determines whether one perfume is eligible to be shown, and then we can use that as a callback of array.filter() on an array of perfumes. We want a perfume to be included if any of its categories are checked (unless you want it to match all the checked categories?). That looks like:

    perfume.categories.some( 
category => activeFilter[category] === true
);

Where .some() loops through the categories and returns true if our callback is true for any category.

I added this to your filteredPerfumes memo and added activeFilter as a dependency so that it will re-filter when the checkboxes change.

  const filteredPerfumes = useMemo(() => {
return data.filter((perfume) =>
perfume.name.toLowerCase().includes(searchPerfume.toLowerCase())
&& perfume.categories.some(
category => activeFilter[category] === true
)
);
}, [data, searchPerfume, activeFilter]);

That works, except that nothing shows when no categories are checked -- whoops! So we want to add a special case that says "all perfumes pass the category filter if no categories are checked." To do that, we need to know if there are checked categories or not. There's a lot of ways to do that, but here's one:

const hasCategoryFilter = Object.values(activeFilter).includes(true);

We look at all of the values in the activeFilter object and see if it includes any which are true.

Now we need to use this value to only filter based on categories when it's true. I'll pull our previous logic into a function and add an if statement (note: the boolean operator || is shorter to use, but I think the if is more readable).

    const matchesCategories = (perfume) => {
if ( hasCategoryFilter ) {
return perfume.categories.some(
category => activeFilter[category] === true
);
} else return true;
}

Sidenote: we have two independent filters, one for search and one for category, so we could call data.filter once and check for both conditions at once or twice and check each condition separately. It does not matter which you do.

The final filter is:

  const filteredPerfumes = useMemo(() => {
const hasCategoryFilter = Object.values(activeFilter).includes(true);

const matchesCategories = (perfume) => {
if ( hasCategoryFilter ) {
return perfume.categories.some(
category => activeFilter[category] === true
);
} else return true;
}

return data.filter((perfume) =>
perfume.name.toLowerCase().includes(searchPerfume.toLowerCase())
).filter( matchesCategories );

}, [data, searchPerfume, activeFilter]);

Updated Sandbox



Related Topics



Leave a reply



Submit