0

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.

Hubert S
  • 31
  • 2

2 Answers2

0

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
}


ᴓᴓᴓ
  • 1,178
  • 1
  • 7
  • 18
  • Hi. Thank you for your response. I tried to do this like you, but it returns an empty array now. Also filterValue is initialized with an empty array. If it's empty I want I want to all the products to show up. – Hubert S Mar 01 '22 at 19:21
  • I would just add a base case for that where you don't filter the results if `filterValue.length === 0`. I've added an example. – ᴓᴓᴓ Mar 01 '22 at 19:38
  • Now it works only when filterValue is an empty array. Filtering doesn't work. – Hubert S Mar 01 '22 at 19:46
  • When I copy/paste my code example into console, it only shows two array values (where title = 'test'. If I change it to 'hi' it returns 1 and so on). Does the sample data not match your state data? – ᴓᴓᴓ Mar 01 '22 at 19:52
  • Yeah I used your code on codpen it works like a charm. But trying to adapt it to my project it just returns an empty array. – Hubert S Mar 01 '22 at 20:06
0

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])
Nina Scholz
  • 376,160
  • 25
  • 347
  • 392