I’m trying to filter my array by a select value but I’m not able to get the result I’m looking for. I want to be able to click a select
option and the data re-renders with content relevant to that option but data is not displaying.
Here is an example of the array I’m trying to filter, I’m specifically targeting the contentType
array.
[{
ageRange: [4, 7],
contentType: ['Animal Stories', 'Stories'],
description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
flag: "New to Yoto",
handle: "5-minute-stories-in-the-wild",
id: 1,
imgSet: {sm: {…}, md: {…}, lg: {…}, alt: null},
price: "9.99",
productType: "single-card",
runtime: 1800,
title: "5-Minute Stories: In the Wild"
},
{
ageRange: [3, 10],
contentType: ['Action & Action', 'Stories'],
description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
flag: "New to Yoto",
handle: "5-minute-stories-in-the-wild",
id: 2,
imgSet: {sm: {…}, md: {…}, lg: {…}, alt: null},
price: "3.99",
productType: "single-card",
runtime: 800,
title: "5-Minute Stories: In the Wild"
}]
Here is the code for my page:
import React, { useState, useEffect } from 'react';
import { sortBy } from '../utils/utils';
import { Products } from '../types/types';
import { fetchData } from '../endpoints/fetchData';
import '../index.css';
function App() {
const sortBy: Record<string, any> = {
duration: (prod: Products[]) => prod.sort((a, b) => a.runtime - b.runtime),
price_ascending: (prod: Products[]) =>
prod.sort((a, b) => parseInt(a.price) - parseInt(b.price)),
price_descending: (prod: Products[]) =>
prod.sort((a, b) => parseInt(b.price) - parseInt(a.price)),
zero_to_five: (prod: Products[]) =>
prod.filter((age) => age.ageRange[0] >= 0 && age.ageRange[1] <= 5),
six_to_nine: (prod: Products[]) =>
prod.filter((age) => age.ageRange[0] >= 6 && age.ageRange[1] >= 9),
};
const [products, setProducts] = useState<Products[]>([]);
const [currentPage, setCurrentPage] = useState<number>(1);
const [filterValue, setFilterValue] = useState<string>('');
const [contentTypeValue, setContentTypeValue] = useState<string>('');
const productsPerPage = 20;
const lastIndex = currentPage * productsPerPage;
const firstIndex = lastIndex - productsPerPage;
const filteredProducts = sortBy?.[filterValue]?.(products) ?? products;
const currentProducts = filteredProducts.slice(firstIndex, lastIndex);
const pages = Math.ceil(filteredProducts.length / productsPerPage);
const numbers = [...Array(pages + 1).keys()].slice(1);
useEffect(() => {
fetchData(setProducts);
}, [contentTypeValue]);
const nextPage = () => {
if (currentPage !== lastIndex) {
setCurrentPage(currentPage + 1);
}
};
const prevPage = () => {
if (currentPage !== firstIndex) {
setCurrentPage(currentPage - 1);
}
};
const movePages = (id: number) => {
setCurrentPage(id);
};
const Pagination = () => {
return (
<nav>
<ul>
<li>
<a
href="#"
className={`${currentPage === 1 ? 'none' : ''}`}
onClick={prevPage}>
Prev
</a>
</li>
{numbers.map((number) => {
return (
<li key={number}>
<a
href="#"
className={`${currentPage === number ? 'active' : ''}`}
onClick={() => movePages(number)}>
{number}
</a>
</li>
);
})}
{filteredProducts.length <= 20 ? (
<></>
) : (
<li>
<a
href="#"
className={`${currentPage === numbers.length ? 'none' : ''}`}
onClick={nextPage}>
Next
</a>
</li>
)}
</ul>
</nav>
);
};
const contentTypeList = [
...new Set(products.map((prod) => prod.contentType).flat()),
];
const handleSort = (event: React.ChangeEvent<HTMLSelectElement>) => {
setFilterValue(event?.target.value);
//goto page 1 after sort
setCurrentPage(1);
};
const handleContentTypeSort = (e: React.ChangeEvent<HTMLSelectElement>) => {
const value = e.target.value;
setContentTypeValue(value);
if (value) {
return products.filter((content: Products) =>
content.contentType.includes(contentTypeValue),
);
}
};
const mapContentTypes = () => {
return (
<>
<select
name="type"
defaultValue=""
data-testid="select"
onChange={(e) => handleContentTypeSort(e)}>
<option value="" selected defaultValue="Sort By:" disabled hidden>
Content type
</option>
{contentTypeList.map((contentType) => (
<option data-testid="type-option" value={contentType}>
{contentType}
</option>
))}
</select>
</>
);
};
const mapProducts = () => {
return (
<React.Fragment>
<div className="container">
<div className="filterContainer">
<div>
<Pagination />
</div>
<div className="filter">
<label htmlFor="type">Sort by: </label>
<select
name="type"
defaultValue=""
data-testid="select"
onChange={handleSort}>
<option
value=""
selected
defaultValue="Sort By:"
disabled
hidden>
Sort By
</option>
<option data-testid="type-option" value="price_ascending">
Price ascending
</option>
<option data-testid="type-option" value="price_descending">
Price descending
</option>
<option data-testid="type-option" value="duration">
Duration
</option>
<option data-testid="type-option" value="zero_to_five">
0-5
</option>
<option data-testid="type-option" value="six_to_nine">
6-9+
</option>
</select>
<label htmlFor="type">Content Type: </label>
{mapContentTypes()}
<button>Reset filter</button>
</div>
</div>
{currentProducts.map((product: Products) => {
return (
<div className="item" key={product.id}>
<img className="image" src={product.imgSet.sm.src} />
<div className="innerContainer">
<span>Name: {product.title}</span>
<span>Price: {product.price}</span>
<span>Stock level: {product.productType}</span>
</div>
</div>
);
})}
<Pagination />
</div>
</React.Fragment>
);
};
return (
<header className="App-header">
<div className="container">{mapProducts()}</div>
</header>
);
}
export default App;
Within this function here is where I’m trying to filter and trigger a re-render for my new data but the data is not displaying.
const handleContentTypeSort = (e: React.ChangeEvent<HTMLSelectElement>) => {
const value = e.target.value;
setContentTypeValue(value);
if (value) {
return currentProducts.filter((content: Products) =>
content.contentType.includes(contentTypeValue),
);
}
};
I have pagination and other sorting functions working but this function doesn’t seem to working like I hope. I just need to able to click and it displays data to the relevant data per the contentType.
I have created a codeandsandbox to explain my code is doing and what is going wrong: https://codesandbox.io/s/runtime-night-w65s8z?file=/src/App.js
2
Answers
Right now you return the filtered list of products in your change handler, which doesn’t do anything.
Instead, you should filter the list of products as you’re rendering. For example:
You’ll need to account for a non valid value like you do in the change handler, but just keeping it simple for the snippet.
In your
handleContentTypeSort
, you are filtering thecurrentProducts
but not updating the state with the filtered array. This is why your data is not re-rendering with the filtered content.The, do like this;
Here, we’re creating a new array
filteredProducts
that includes only the products whosecontentType`` includes the selected value. Then, we're updating the state of
productswith
setProducts(filteredProducts)“`, so it will trigger a re-render of your component with the filtered products.