'Filtering array of objects based on multiple strings added dynamically to another array [duplicate]

I'm trying to create product filter function for my store project.

Now it looks like this

    useEffect(() => {
        let newClothesData = [...clothesData]
        let newFilteredData
        const filterArray = () => {
            newFilteredData = newClothesData
                .filter(({ title }) => title.toLowerCase().includes(filterValue))
                .map(({ title, id, price, image, description }) => ({ title, id, price, image, description }))
            setFilteredData(newFilteredData)
        }
        filterArray()
    }, [clothesData, filterValue])

filterValue is an empty array which is being updated by pressing checkboxes, then it becomes array of strings, (f.e. ['backpack', 'jacket']. What I want to do is to filter newClothesData, which is an array of objects, and check if object's key title matches any of these strings stored in filterValue's array.

F.e. If user presses backpacks and jackets checkboxes, function will fill newFilteredData with objects that have backpack or jacket indluced in their title key value.

I don't really know how to achieve this. At this moment filter works only for 1 or 0 string being stored in filterValue array

EDIT:

import React, { useState, useEffect } from 'react'
import ProductItem from './ProductItem/ProductItem'
import { useSelector } from 'react-redux'
import './ProductList.css'

const ProductList = () => {
    const clothesData = useSelector(state => state.fetchClothes.clothes)
    const sortType = useSelector(state => state.sortingClothes.sortAs)
    const filterValue = useSelector(state => state.filteringClothes.filterValue)
    const [filteredData, setFilteredData] = useState([])
    const [sortedData, setSortedData] = useState([])

    useEffect(() => {
        let newClothesData = [...clothesData]
        const filter = new Set(filterValue)

        if (filterValue.length === 0) {
            setFilteredData(newClothesData)
        } else {
            let newFilteredData = newClothesData
                .filter(({ title }) => filterValue.includes(title.toLowerCase()))
                .map(({ title, id, price, image, description }) => ({ title, id, price, image, description }))
            setFilteredData(newFilteredData)
            console.log(newFilteredData)
        }
    }, [clothesData, filterValue])

    useEffect(() => {
        let newClothesData = [...filteredData]
        const sortArray = type => {
            const types = {
                none: 'none',
                priceAsc: 'price',
                priceDes: 'price',
                titleAsc: 'title',
                titleDes: 'title',
            }

            const sortProperty = types[type]
            if (sortType === 'priceAsc') {
                return newClothesData.sort((a, b) => (a[sortProperty] > b[sortProperty] ? 1 : -1))
            } else if (sortType === 'priceDes') {
                return newClothesData.sort((a, b) => (a[sortProperty] < b[sortProperty] ? 1 : -1))
            } else if (sortType === 'titleAsc') {
                return newClothesData.sort((a, b) => (a[sortProperty].toUpperCase() > b[sortProperty].toUpperCase() ? 1 : -1))
            } else if (sortType === 'titleDes') {
                return newClothesData.sort((a, b) => (a[sortProperty].toUpperCase() < b[sortProperty].toUpperCase() ? 1 : -1))
            } else {
                return newClothesData
            }
        }
        setSortedData(newClothesData)
        sortArray(sortType)
    }, [sortType, filteredData])
    return (
        <div className='product-list'>
            {sortedData.map(item => {
                return <ProductItem key={item.id} clothesData={item} />
            })}
        </div>
    )
}

export default ProductList

This is my entire component. It fetches to the redux store data from fakestore api and it gets items from clothes category only. I get this clothes data with useSelector in ProductList component aswell as the current filterValue. clothesData has 10 objects in an array.



Solution 1:[1]

I would refactor so that filterValue is an empty array of strings rather than switching it from an empty string to a string[]. Seems easy to do; just initialize it with [] ?

example (removed useEffect and state setting as it's irrelevant imo):

let newClothesData = [{title:'hi', id: 'hi', price: 'hi', image: 'hi', description: 'hi'}, {title:'hello world', id: 'hi', price: 'hi', image: 'hi', description: 'hi'}, {title:'test', id: 'hi', price: 'hi', image: 'hi', description: 'hi'}, {title:'test', id: 'test2', price: 'test2', image: 'test2', description: 'test2'}]
let filterValue = ['hi']

let newFilteredData
const filterArray = () => {
    newFilteredData = newClothesData
        .filter(({ title }) => filterValue.some(value => value.toLowerCase().includes(title.toLowerCase())))
        .map(({ title, id, price, image, description }) => ({ title, id, price, image, description }))
    
}
filterArray()

console.log(newFilteredData)

edit: There's actually no reason to use a function here either, unless you plan to re-use it.

let newClothesData = [{title:'hi', id: 'hi', price: 'hi', image: 'hi', description: 'hi'}, {title:'hello world', id: 'hi', price: 'hi', image: 'hi', description: 'hi'}, {title:'test', id: 'hi', price: 'hi', image: 'hi', description: 'hi'}, {title:'test', id: 'test2', price: 'test2', image: 'test2', description: 'test2'}]
let filterValue = ['test']

let newFilteredData = newClothesData
        .filter(({ title }) => filterValue.some(value => value.toLowerCase().includes(title.toLowerCase())))
        .map(({ title, id, price, image, description }) => ({ title, id, price, image, description }))
  

console.log(newFilteredData)

edit2: if you want empty array to show all values, I would just consider that a base case.

let newClothesData = [{title:'hi', id: 'hi', price: 'hi', image: 'hi', description: 'hi'}, {title:'hello world', id: 'hi', price: 'hi', image: 'hi', description: 'hi'}, {title:'test', id: 'hi', price: 'hi', image: 'hi', description: 'hi'}, {title:'test', id: 'test2', price: 'test2', image: 'test2', description: 'test2'}]
let filterValue = ['test']

if (filterValue.length === 0) {
 // set state as newClothesData as we don't need to filter
} else {
    let newFilteredData = newClothesData
        .filter(({ title }) => filterValue.some(value => 
value.toLowerCase().includes(title.toLowerCase())))
        .map(({ title, id, price, image, description }) => ({ title, id, price, image, description }))
    console.log(newFilteredData)
    // set state after filtering
}


Solution 2:[2]

You could filter by looking into the array with Array#includes and short the complete code a bit.

useEffect(() => {
    setFilteredData(clothesData
        .filter(({ title }) => filterValue.includes(title.toLowerCase()))
        .map(({ title, id, price, image, description }) => ({ title, id, price, image, description }))
    );
}, [clothesData, filterValue])

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2