skip to Main Content

I’m learning React, I’m still a beginner and I’m encountering an issue while attempting to sort an array of objects in a React component. The error message I’m receiving is "Cannot read properties of undefined (reading ‘price’). This happen only when I use UseState, I tried not to use it and use the sorting’s comparison function directly let productsPrice = productData.sort((a, b) => a.price - b.price)) and it worked very well.

Code:

import '../style/products.css';
import productData from '../products-data';
import Product from './Product';
import { useState } from 'react';

function ProductsList() {
  const compareAscendingPrice = (a, b) => a.price - b.price;
  const compareAscendingRate = (a, b) => a.rating.rate - b.rating.rate;
  const compareAscendingAlpha = (a, b) => a.title.localeCompare(b.title, 'en');

  // let type = compareAscendingAlpha;
  let [type, SetType] = useState(compareAscendingPrice); // useState hook
  let productsPrice = productData.sort(type);
  let products = [...productsPrice].map((item) => {
    return <Product item={item} key={item.id} />;
  });

  return (
    <>
      <div className="products__filter">
        <div className="products__filter__search">
          <input type="text" placeholder="Search" />
        </div>
        <div className="products__filter__sort">
          <label htmlFor="sort">Sort by:</label>
          <select
            name="sort"
            id="sort"
            onChange={(e) => {
              if (e.target.value === 'price') {
                SetType(compareAscendingPrice);
              } else if (e.target.value === 'rating') {
                SetType(compareAscendingRate);
              } else if (e.target.value === 'title') {
                SetType(compareAscendingAlpha);
              }
            }}
          >
            <option value="price">Price</option>
            <option value="rating">Rating</option>
            <option value="title">Title</option>
          </select>
          <div className="products__filter__direction">
            <label htmlFor="ascending">Ascending</label>
            <input type="checkbox" name="ascending" id="ascending" />
            <label htmlFor="descending">Descending</label>
            <input type="checkbox" name="descending" id="descending" />
          </div>
        </div>
      </div>
      <div className="products">{products}</div>;
    </>
  );
}

export default ProductsList;

What could be causing this issue, and how can I ensure that the price property is properly recognized during sorting?

I checked the data files, I looked on the internet, but I did not find any solution.

Note: I’m learning React I’m still a beginner.

2

Answers


  1. You haven’t defined price in compareAscendingPrice arrow function. That’s the issue. First of all, you have to get the data from array!

    Login or Signup to reply.
  2. There are two problems there (well, really two different versions of the same problem): Both useState itself and the state setter you get from it expect that when you pass a function into them, they’re supposed to call that function rather than store it. For useState itself, it expects to call that function only during the initial mount (docs). For the state setter, it expects that you want to queue a state update callback (docs). You can’t directly store functions in state.

    You have at least a couple of alternatives:

    1. Store a flag that you’ll then use to pass to a generalized sort function that knows which of those sort functions to call.

      or

    2. Store an object with the desired sort callback as a property.

    Here’s an example of #1:

    const comparisons = {
        price: (a, b) => a.price - b.price,
        rating: (a, b) => a.rating.rate - b.rating.rate,
        value: (a, b) => a.title.localeCompare(b.title, 'en'),
    };
    
    function ProductsList() {
        // ...
        const [sortType, setSortType] = useState("price");
        let productsPrice = productData.sort(comparisons[type]);
        // ...
    

    Then when setting it:

    <select
    name="sort"
    id="sort"
        onChange={(e) => {
          setSortType(e.target.value);
        }}
    >
    

    There I’ve used e.target.value directly by making the option values the keys of the comparisons object, but you could continue to use a switch if you preferred.

    (I changed type to sortType to be more specific, and used an initial lower-case letter because by convention, functions with upper-case initial letters are constructor functions.)

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