skip to Main Content

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


  1. 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:

    {currentProducts
      .filter((content) => {
        return content.contentType.includes(contentTypeValue);
      })
      .map((product) => {
        return <div className="item" key={product.id}>...</div>;
      })}
    

    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.

    Login or Signup to reply.
  2. In your handleContentTypeSort, you are filtering the currentProducts 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;

    const handleContentTypeSort = (e: React.ChangeEvent<HTMLSelectElement>) => {
       const value = e.target.value;
       setContentTypeValue(value);
    
       if (value) {
         const filteredProducts = products.filter((content: Products) =>
           content.contentType.includes(value),
         );
         setProducts(filteredProducts);
       }
    };
    

    Here, we’re creating a new array filteredProducts that includes only the products whose contentType`` includes the selected value. Then, we're updating the state of productswithsetProducts(filteredProducts)“`, so it will trigger a re-render of your component with the filtered products.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search