I have been trying to set up a filter on an array of items so that when the user clicks a checkbox, the list will display only items based on their selection. It needs to allow for multiple selections so that ANY (not ALL) of the multiple conditions are applied to the filter.
The code below is what I have, but it only applies the filter to the first checkbox, everytime I select a second, or third checkbox, the filter doesn't change.
let filterOptions = document.querySelectorAll(".filter_checkbox")
var filterList = [];
const handleFilterCheckbox = (e) => {
for(var i=0; filterOptions[i]; i ){
if(filterOptions[i].checked){
filterList.push(filterOptions[i].value);
}
}
console.log(filterList)
var getFilteredResults = hotelsList.filter(function (result) {
for(var i=0; i < filterList.length; i ){
return filterList.indexOf(result.locationName) >= 0;
}
});
if(filterList.length > 0){
setHotelsList(getFilteredResults)
}else{
setHotelsList(hotels)
}
}
UPDATE: Below is the full code
import { useState } from 'react'
import './cards.styles.css'
import Card from '../card/Card'
import hotels from '../../data/hotels_array'
export default function Cards(){
const [hotelsList, setHotelsList] = useState(hotels)
const handleSortChange = (e) => {
const sortValue = e.target.value
const copyHotelsList = [...hotelsList]
if(sortValue === "sort-default"){
setHotelsList(hotels)
}else{
copyHotelsList.sort((a, b) => {
if(sortValue === "sort-price-lh"){
return a.fromPrice - b.fromPrice
}else if (sortValue === "sort-price-hl"){
return b.fromPrice - a.fromPrice
}else if(sortValue === "sort-review-score"){
return b.guestRating - a.guestRating
}
})
setHotelsList(copyHotelsList)
}
}
const hotelCards = hotelsList.map(hotel =>{
return (
<Card
key={hotel.hotelId}
{...hotel}
/>
)
})
const hotelsListUniqueByLocation = [...new Map(hotels.map(item => [item.locationName, item])).values()];
let filterOptions = document.querySelectorAll(".filter_checkbox")
var filterList = [];
const handleFilterCheckbox = (e) => {
for(var i=0; filterOptions[i]; i ){
if(filterOptions[i].checked){
filterList.push(filterOptions[i].value);
}
}
console.log(filterList)
var getFilteredResults = hotelsList.filter(function (result) {
for(var i=0; i < filterList.length; i ){
return filterList.indexOf(result.locationName) >= 0;
}
});
if(filterList.length > 0){
setHotelsList(getFilteredResults)
}else{
setHotelsList(hotels)
}
}
const hotelLocations = hotelsListUniqueByLocation.map(location =>{
if(location.locationName != ""){
return (
<li key={location.locationName}>
<input type="checkbox" id={location.locationName} value={location.locationName} onChange={handleFilterCheckbox} className="filter_checkbox" />
<label htmlFor={location.locationName} className='fs12p'>{location.locationName}</label>
</li>
)
}
})
return(
<div className="results_container">
<aside>
<h3>Filter</h3>
Sorted by:
<select onChange={handleSortChange}>
<option value="sort-default">Recommended</option>
<option value="sort-price-lh">Price (low to high)</option>
<option value="sort-price-hl">Price (high to low)</option>
<option value="sort-review-score">Review score</option>
</select>
<div>
<input type="checkbox" value="wififree" id="wififree" className="filter_checkbox" />
<label htmlFor="wififree" className='fs12p'>Free Wi-Fi</label>
</div>
<div>
<ul className='hotel_locations'>
{hotelLocations}
</ul>
</div>
</aside>
<section className='cards_container container'>
{hotelCards}
</section>
</div>
)
}
CodePudding user response:
I think the problem is in the getFilteredResults
you are going over the filters but in case one of them matches, you return true
and by that, not checking the other filters
for(var i=0; i < filterList.length; i ){
return filterList.indexOf(result.locationName) >= 0;
}
What you can do in order to make sure all your filters are match, is to use the every
function
var getFilteredResults = hotelsList.filter(hotel =>
filterList.every(filter => filter === hotel.locationName)
)
CodePudding user response:
The key is to store the filter as state. Your checkboxes update that state, and then you can use functions to produce the relevant data: values for your checkboxes, and to control whether they're checked or not, and objects for your hotel list.
I wrote this before you added your full code, so it differs a little, but it should show how checkbox location filters can narrow down a list of hotels.
Note 1: I don't have the functions inside the component because I can pass through the various states to them as arguments. Primarily it means you can decouple them into helper files if needed to make your components a little more concise and clean.
Note 2: I'm using event delegation here to catch checkbox events as they "bubble up" the DOM, but adding individual listeners to the checkboxes is fine too - this is just a little more succinct.
const { Fragment, useState } = React;
// Filter the hotels by location
function filterHotels(hotels, filter) {
return hotels.filter(hotel => {
return filter.includes(hotel.location);
});
}
// Get a deduped list of hotels by mapping their
// locations, adding them to a set, and sorting them
// alphabetically
function getLocations(hotels) {
const locations = hotels.map(hotel => hotel.location);
return [...new Set(locations)].sort();
}
// Works out if a checkbox is checked
function isChecked(filter, location) {
return filter.includes(location);
}
// Based on the checkbox checked value return
// a new filter array with a checkbox value added, or a
// new filter array with a checkbox's value removed
function getUpdated(filter, value, checked) {
return checked
? [...filter, value]
: filter.filter(el => el !== value);
}
// For the purposes of this example I'm just passing in
// and array of predefined hotel objects
function Example({ config }) {
// Initialise the hotels and filter states
const [ hotels, setHotels ] = useState(config);
const [ filter, setFilter ] = useState(getLocations(config));
// The one function required in the component is the
// one that updates the state. Because I've used event delegation
// it first checks to see if the clicked element is a checkbox
// and then sets the filter state with a new filter array using
// the checkbox values
function handleClick(e) {
if (e.target.matches('[type="checkbox"]')) {
const { value, checked } = e.target;
setFilter(getUpdated(filter, value, checked));
}
}
// Add some components that accepts forms of state
// Note the click listener on the `section` element.
// Using event delegation it catches events from its child
// elements as they "bubble up" the DOM - the handler works out
// if they're from the checkboxes and then processes the values
return (
<section onClick={handleClick}>
<Filter locations={getLocations(hotels)} filter={filter} />
<HotelList hotels={hotels} filter={filter} />
</section>
);
}
function Filter({ locations, filter }) {
return (
<section>
{locations.map((location, id) => {
return (
<Fragment key={id}>
<label htmlFor={location}>
{location}
</label>
<input
id={location}
type="checkbox"
value={location}
checked={isChecked(filter, location)}
/>
</Fragment>
);
})}
</section>
);
}
function HotelList({ hotels, filter }) {
return (
<ul>
{filterHotels(hotels, filter).map(hotel => {
return (
<li key={hotel.id}>
{hotel.name} ({hotel.location})
</li>
);
})}
</ul>
);
}
const config=[{id:1,name:"York Hotel",location:"York"},{id:2,name:"London Hotel",location:"London"},{id:3,name:"Bork Hotel",location:"York"},{id:4,name:"Fancy Hotel",location:"London"},{id:5,name:"Pelican Hotel",location:"Paris"},{id:6,name:"Moose Hotel",location:"Paris"},{id:7,name:"Murder Hotel",location:"Chicago"}];
ReactDOM.render(
<Example config={config} />,
document.getElementById('react')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js"></script>
<div id="react"></div>