React не может инициировать изменение состояния использования в нужное время

Я пытаюсь сортировать продукты по цене: низкая-высокая или высокая-низкая, что я делал раньше в vanilla JS, но на этот раз в React. У меня есть раскрывающийся список, в котором можно выбрать желаемый порядок, и сама функция работает, но не так, как мне нужно.

В моей локальной версии кода, если вы выбираете порядок сортировки из раскрывающегося списка, в этот момент он ничего не делает, однако, если вы затем фильтруете продукты, скажем, по бренду или категории, он применяет как фильтр бренда (или категории), так и выбранный фильтр порядка сортировки цен. Затем вы можете легко изменить порядок сортировки с низкого-высокого на высокий-низкий или наоборот.

Таким образом, кажется, что выбранный порядок сортировки применяется правильно, но он просто не отображает изменение useState во время выбора в раскрывающемся списке, и я не знаю, почему.

Вкратце, вот биты, управляющие функциональностью порядка сортировки:

const [filterData, setFilterData] = useState(productDataList.products || []);

const sortItems = (e) => {
console.info('Sort');
const sortedProducts = filterData.sort((a, b) => {
  if (e.value == "LowToHigh") {
    console.info('sorted low to high');
    if (a.price < b.price) {
      return -1;
    } else {
      return 1;
    }
  } else if (e.value == "HighToLow") {
    console.info('sorted high to low');
    if (a.price > b.price) {
        return -1;
      } else {
        return 1;
      }
    }
  });
  setFilterData(sortedProducts);
};

return (

<select id = "sortDropDown" onChange = {(e) => sortItems(e.target)}>
    <option value = "default">Sort by:</option>
    <option value = "LowToHigh">Low to high</option>
    <option value = "HighToLow">High to low</option>
</select>

)

Надеюсь, этого будет достаточно, чтобы кто-то помог предоставить информацию об исправлении, поскольку я не понимаю, почему оно не обновляется при выборе варианта из раскрывающегося списка.

Если нет, я добавил больше кода ниже для страницы (хотя это слегка урезанная версия и обычно разбивается на несколько компонентов, и я попытался объединить его ниже, но если что-то еще необходимо, чтобы иметь возможность помочь пожалуйста, дайте мне знать.

Заранее спасибо.

const {useState, useEffect}  = React

const productDataList = {
  "products": [
    {
      "id": 1,
      "title": "Essence Mascara Lash Princess",
      "category": "beauty",
      "price": 9.99,
      "brand": "Essence",
      "thumbnail": "https://cdn.dummyjson.com/products/images/beauty/Essence%20Mascara%20Lash%20Princess/thumbnail.png"
    },
    {
      "id": 2,
      "title": "Eyeshadow Palette with Mirror",
      "category": "beauty",
      "price": 19.99,
      "brand": "Glamour Beauty",
      "thumbnail": "https://cdn.dummyjson.com/products/images/beauty/Eyeshadow%20Palette%20with%20Mirror/thumbnail.png"
    },
    {
      "id": 3,
      "title": "Powder Canister",
      "category": "beauty",
      "price": 14.99,
      "brand": "Velvet Touch",
      "thumbnail": "https://cdn.dummyjson.com/products/images/beauty/Powder%20Canister/thumbnail.png"
    },
    {
      "id": 4,
      "title": "Dior J'adore",
      "category": "fragrances",
      "price": 89.99,
      "brand": "Dior",
      "thumbnail": "https://cdn.dummyjson.com/products/images/fragrances/Dior%20J'adore/thumbnail.png"
    },
    {
      "id": 5,
      "title": "Dolce Shine Eau de",
      "category": "fragrances",
      "price": 69.99,
      "brand": "Dolce & Gabbana",
      "thumbnail": "https://cdn.dummyjson.com/products/images/fragrances/Dolce%20Shine%20Eau%20de/thumbnail.png"
    },
    {
      "id": 6,
      "title": "Annibale Colombo Sofa",
      "category": "furniture",
      "price": 2499.99,
      "brand": "Annibale Colombo",
      "thumbnail": "https://cdn.dummyjson.com/products/images/furniture/Annibale%20Colombo%20Sofa/thumbnail.png"
    },
  ],
  "total": 194,
  "skip": 0,
  "limit": 36
}

const App = () => {

  const [productsLimit, setProductsLimit] = useState(productDataList.limit)
  const [filterData, setFilterData] = useState(productDataList.products || []);
  const [categories, setCategories] = useState([]);
  const [brands, setBrands] = useState([]);

  useEffect(() => {
    let filteredCategories = [],
        filteredBrands = [];
        productDataList.products.forEach((product) => {
      if (!filteredCategories.includes(product.category)) {
        filteredCategories.push(product.category);
      }
      if (!filteredCategories.includes(product.brand)) {
        filteredBrands.indexOf(product.brand) == -1 && product.brand != '' && product.brand != undefined && product.brand != null ? filteredBrands.push(product.brand) : null;
      }
    });
    setCategories(filteredCategories);
    setBrands(filteredBrands);
  }, []);

  const productFilters = (products, filt) => {
    let filteredProducts = [];
    if (categories.includes(filt)) {
      filteredProducts = products.category.toLowerCase() === filt.toLowerCase();
    } else if (brands.includes(filt)) {
      filteredProducts = products.brand === filt;
    }
    return filteredProducts;
  };

  const filter = (byFilter) => {
    if (byFilter) {
      let productsData = [...productDataList.products];
      productsData = productsData.filter((filter) => {
        return productFilters(filter, byFilter);
      });
      setFilterData(productsData);
    } else {
      setFilterData(productDataList.products);
    }
  };

  const sortItems = (e) => {
    console.info('Sort');
      const sortedProducts = filterData.sort((a, b) => {
        if (e.value == "LowToHigh") {
          console.info('sorted low to high');
          if (a.price < b.price) {
            return -1;
          } else {
            return 1;
          }
        } else if (e.value == "HighToLow") {
          console.info('sorted high to low');
          if (a.price > b.price) {
              return -1;
            } else {
              return 1;
            }
        }
      });
      setFilterData(sortedProducts);
  };

  return (

    <div>
      <select id = "sortDropDown" onChange = {(e) => sortItems(e.target)}>
        <option value = "default">Sort by:</option>
        <option value = "LowToHigh">Low to high</option>
        <option value = "HighToLow">High to low</option>
      </select>
      <select onChange = {(e) => filter(e.target.value)}>
        <option value = "Filter Categories" selected = "selected">Filter by categories</option>
        {categories.map((catFilter) => (
          <option value = {catFilter}>{catFilter}</option>
        ))}
      </select>
      <select onChange = {(e) => filter(e.target.value)}>
        <option value = "Filter Categories" selected = "selected">Filter by brands</option>
        {brands.map((item) => (
          <option value = {item}>{item}</option>
        ))}
      </select>
      <button onClick = {filter}>Clear filter</button>
    


      <div className='products'>

        {filterData?.slice(0, productsLimit).map(product => (
          <div className='product' key = {product.id}>
            <div className='productImage'>
              <img src = {product.thumbnail} />
            </div>
            <div className='productInfo'>
              <div className='productTitle'>
                <div className='productBrand'>{product.brand}</div> 
                <div className='productName'>{product.title}</div>
                <div className='productName'>{product.title}</div>
              </div>
              <div className='productPrice'>Price: £{product.price}</div>
              {}
            </div>

          </div>
        ))}
      </div>
    </div>
  )
}


ReactDOM.render(<App />, document.querySelector('#root'));
.products {
  display: flex;
  flex-wrap: wrap;
}
<script src = "https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
<script src = "https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>

<div id = "root"></div>

🤔 А знаете ли вы, что...
С помощью JavaScript можно валидировать данные на стороне клиента, что улучшает пользовательский опыт.


50
3

Ответы:

Ваше понимание React неверно. пожалуйста, укажите правильный подход ниже.

// const [filterData, setFilterData] = useState(productDataList.products || []);
let [orderType,setOrderType] = useState("default")
let filterData = useMemo(()=>{
  // write your order code with orderType ...
},[orderType])


return (

<select onChange = {(e) => setOrderType(e.target)}>
    <option value = "default">Sort by:</option>
    <option value = "LowToHigh">Low to high</option>
    <option value = "HighToLow">High to low</option>
</select>

)

Спасибо @Robin Zigmond за его комментарий, просто для более быстрого получения ответа я добавляю его сюда.

Изменение .sort на .toSorted мгновенно решило проблему.

const sortItems = (e) => {
  console.info('Sort');
  const sortedProducts = filterData.toSorted((a, b) => {
    if (e.value == "LowToHigh") {
      console.info('sorted low to high');
      if (a.price < b.price) {
        return -1;
      } else {
        return 1;
      }
    } else if (e.value == "HighToLow") {
      console.info('sorted high to low');
      if (a.price > b.price) {
          return -1;
        } else {
          return 1;
        }
      }
  });
  setFilterData(sortedProducts);
};

Это объясняется тем, что:

sort изменяет массив и возвращает ту же ссылку (см. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort ), поэтому React «не видит» «Любое изменение. Самое простое решение, если вам не нужна поддержка браузеров старше года или около того, — использовать новый toSorted https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/ Global_Objects/Array/toSorted] вместо sort

Я по-прежнему считаю, что решение/обходной путь, который будет работать со старыми браузерами, будет полезным, и я рад отметить это как ответ, если оно будет добавлено, но сейчас спасибо @Robin Zigmond за это решение.


Решено

Вы можете установить состояние в скопированном массиве с помощью:

const sortItems = (e) => {
  console.info('Sort');
  const priceFilterData = [...filterData]
  priceFilterData.sort((a, b) => {
    if (e.value == "LowToHigh") {
      console.info('sorted low to high');
      if (a.price < b.price) {
        return -1;
      } else {
        return 1;
      }
    } else if (e.value == "HighToLow") {
      console.info('sorted high to low');
      if (a.price > b.price) {
          return -1;
        } else {
          return 1;
        }
      }
  });
  setFilterData(sortedProducts);
};

Это позволит обойти изменение исходного массива, используемого для вашего useState, которое происходило раньше.