网站搜索

javascript - 在 React 中映射数组时使用了上一次渲染的值


我正在为 React 构建一个自定义 Hook,它会对数组应用过滤(按类别)和搜索。

const amountSmallestFirst = (a, b) => {
  return a.amount - b.amount;
};

const amountLargestFirst = (a, b) => {
  return b.amount - a.amount;
};

const alphabeticallySorted = (a, b) => {
  if (a.name < b.name) {
    return -1;
  }
  if (a.name > b.name) {
    return 1;
  }
  return 0;
};

const alphabeticallySortedReverse = (a, b) => {
  if (a.name > b.name) {
    return -1;
  }
  if (a.name < b.name) {
    return 1;
  }
  return 0;
};

const useSearchAndFilter = (data: any, isLoading: boolean, sortBy: SortBy) => {
  const [search, setSearch] = useState<string>('');
  const [currentFilter, setFilter] = useState<SortBy>(sortBy);
  const [result, setResult] = useState<any>([]);

  const handleSearch = (event: React.FormEvent<HTMLInputElement>) => {
    const target = event.target as HTMLInputElement;
    setSearch(target.value);
  };

  const selectSorting = (sortBy = SortBy.AMOUNT_LARGEST_FIRST) => {
    switch (sortBy) {
      case 'AMOUNT_SMALLEST_FIRST':
        return amountSmallestFirst;
      case 'AMOUNT_LARGEST_FIRST':
        return amountLargestFirst;
      case 'ALPHABETICALLY_SORTED':
        return alphabeticallySorted;
      case 'ALPHABETICALLY_SORTED_REVERSE':
        return alphabeticallySortedReverse;
      default:
        return amountLargestFirst;
    }
  };

  const applySearchAndFilter = (data: any, sortBy = SortBy.AMOUNT_LARGEST_FIRST) => {
    if (!data) return null;
    let results;
    if (search !== '') {
      results = data.filter(({ name }: Name) => name.toLowerCase().includes(search.toLowerCase()));
    }
    const isNumber = parseInt(search);
    if (isNumber) {
      results = data.filter(({ amount }: Name) => amount === isNumber);
    }
    if (!results) {
      results = data;
    }
    results.sort(selectSorting(sortBy));
    setResult(results);
  };

  useLayoutEffect(() => {
    applySearchAndFilter(data, currentFilter);
  }, [isLoading, search, currentFilter]);

  return { result, search, setFilter, handleSearch, currentFilter };
};

export { useSearchAndFilter };

当我搜索某些内容时它工作正常,但当搜索未启用时,过滤会落后一拍。我开始通过跟踪数组何时变化来调试这个问题,并遇到了一些奇怪的行为:数据在我开始将数组映射到较小组件之前都能正常工作。

  console.log(nameList);
  const mapListItems = nameList.map((item: Name, i: number) => {
    console.log(item, nameList);
    return <ListItem key={item.name} item={item} color={i % 2 === 0 ? 'secondary' : 'primary'} />;
  });

nameList 日志既相同又是更新后的数组,但传递给组件的 props 却是上一次渲染的数组信息?

这是如何可能的,以及像这样的情况如何可以被解决?

我尝试过实现回调函数以及使用 useEffect 和 useLayout 钩子,但两者都失败了。

编辑:

Baymax 请求 setFilter 实现

import React, { useState, useEffect, useLayoutEffect } from 'react';
import { ReactComponent as OpenIcon } from '../assets/open.svg';
import { SortBy } from '../types';
import Togglable from './Togglable';

interface FilterProps {
  setFilter: any;
  currentFilter: SortBy;
}

const Filter: React.FC<FilterProps> = ({ setFilter }) => {
  const [show, setShow] = useState<boolean>(false);
  const [current, setCurrent] = useState<SortBy | null>(SortBy.AMOUNT_LARGEST_FIRST);

  useLayoutEffect(() => {
    setFilter(current);
    setShow(false);
  }, [current]);

  const handleFilterChange = (value: SortBy) => {
    setCurrent(value);
  };

  return (
    // Relative parent needed for the togglable selection which is absolute
    <div className='relative'>
      <div
        className='m-w-30 h-11 px-3 bg-gray-800 text-white flex rounded-lg justify-between items-center flex-nowrap space-x-2 cursor-pointer'
        onClick={() => setShow(!show)}
      >
        <p className='flex-none'>Filter by</p>
        <OpenIcon className={`w-3 fill-current transition duration-300 linear text-white ${show ? 'arrowDown' : 'arrowUp'}`} />
        <Togglable show={show}>
          <ul className='bg-gray-800 absolute text-gray-300 -h-full mt-6  w-full -m-5 z-10 rounded-lg sm:w-48 sm:-ml-28'>
            <li
              className={`p-2  hover:text-white rounded-t-lg ${current === SortBy.AMOUNT_LARGEST_FIRST && 'bg-gray-700'} `}
              onClick={() => handleFilterChange(SortBy.AMOUNT_LARGEST_FIRST)}
            >
              Highest amount first
            </li>
            <li
              className={`p-2 hover:text-white ${current === SortBy.AMOUNT_SMALLEST_FIRST && 'bg-gray-700'}`}
              onClick={() => handleFilterChange(SortBy.AMOUNT_SMALLEST_FIRST)}
            >
              Lowest amount first
            </li>
            <li
              className={`p-2 hover:text-white ${current === SortBy.ALPHABETICALLY_SORTED && 'bg-gray-700'}`}
              onClick={() => handleFilterChange(SortBy.ALPHABETICALLY_SORTED)}
            >
              Alphabetically sorted
            </li>
            <li
              className={`p-2 hover:text-white rounded-b-lg ${current === SortBy.ALPHABETICALLY_SORTED_REVERSE && 'bg-gray-700'}`}
              onClick={() => handleFilterChange(SortBy.ALPHABETICALLY_SORTED_REVERSE)}
            >
              Alphabetically reversed
            </li>
          </ul>
        </Togglable>
      </div>
    </div>
  );
};

export default Filter;

解决方案:

由于某种原因,直接将 API 数据传递给结果会导致排序出现错误。

    if (!results) {
      results = data;
    }

在将数据的副本传递给结果后,排序开始起作用。

    if (!results) {
      results = [...data];
    }

这对我来说真的很奇怪,因为在控制台打印时,[...data] 和 data 是相同的。