下拉选择组件有数据落后一步

问题描述 投票:0回答:1

我的应用程序中有一个下拉菜单。当它被选择时,它会触发一个更改 sortOption 状态的方法,然后触发 useEffect 对数据进行排序(称为 allTabs)。然而,数据似乎总是落后一步。例如,默认是从新到旧,但它最初显示未排序的数据。如果随后按下一个新选项(例如 A-Z),数据就会按新到旧排序。如果再次按下新选项,例如从旧到新,数据就会按 A-Z 等排序。我不确定为什么会发生这种情况。我看过有关使用回调的帖子,但我对 React、NodeJS 和 TypeScript 还很陌生,我不确定如何将这些答案集成到我的代码中。

下拉菜单如下所示:

{/* Dropdown for sorting */}
<div className="mb-4">
  <label htmlFor="sortOptions" className="mr-2">Sort by:</label>
  <select
    style={{ color: 'black' }}
    id="sortOptions"
    value={sortOption}
    onChange={handleSortChange}
    className="p-2 border rounded"
  >
    <option value="Newest to Oldest">Newest to Oldest</option>
    <option value="Oldest to Newest">Oldest to Newest</option>
    <option value="A-Z">A-Z</option>
    <option value="Z-A">Z-A</option>
  </select>
</div>

这里是handleSortChange方法:

 // Handle dropdown change with type annotation
 const handleSortChange = (event: ChangeEvent<HTMLSelectElement>) => {
   setSortOption(event.target.value);
 };

这里是sortOption的useState,以及sortOption或allTabs(我正在排序的数据)变化时触发的useEffect:

const [sortOption, setSortOption] = useState<string>("Newest to Oldest");

 // sort tabs based on selected option, update if allTabs or sortOption changes
 useEffect(() => {
   console.log(allTabs);
   console.log(sortOption);
   switch (sortOption) {
     case "Newest to Oldest":
       allTabs.sort(compareDates);
       console.log("sorted new to old");
       break;
     case "Oldest to Newest":
       allTabs.sort((a, b) => compareDates(b, a));
       console.log("sorted old to new");
       break;
     case "A-Z":
       allTabs.sort((a, b) => a.name.localeCompare(b.name));
       console.log("sorted A-Z");
       break;
     case "Z-A":
       allTabs.sort((a, b) => b.name.localeCompare(a.name));
       console.log("sorted Z-A");
       break;
     default:
       return;
     }
     console.log(allTabs);
 }, [sortOption, allTabs])

我知道这个 useEffect 中的方法可以正常工作,排序也是如此。

可以看到,useEffect方法中有控制台日志。他们打印出数据和所采取的路径。他们给了我一个有趣的结果。当我在切换之前和之后打印所有选项卡时,它们都会根据打印出来的正确路径进行排序,这意味着一切看起来都是正确的。用户选择的选项确实是打印内容以及数据如何排序。但网站上实际显示的数据仍然是上次选择的数据。因此,我假设更新 allTabs 时数据的呈现不会更新,这是我需要能够做到的。如果我尝试在 useEffect 中实际使用 allTabs 的 setter,它只会导致无限循环,所以我不确定如何做出反应以意识到数据已更新。强制重新加载页面只会重置所有内容,包括数据和排序选项。

编辑: 为了显示数据,我当前正在制作所有选项卡的地图。我应该在组件中执行此操作并使其接受一些数据,以便每当我更改它或其他内容时都可以使用数据调用该组件吗?

这就是数据的显示方式。这发生在应用程序底部的返回中,以及其他一些内容中

{allTabs.map((allTab, index) => (
  <React.Fragment key={index}>
    <Link
      className=""
      href="/"
      onClick={() => {
        localStorage.setItem("tabTitle", allTab.name);
        localStorage.setItem("tabContent", allTab.tab);
      }}
    >
      {allTab.name}
    </Link>
    <span>
      {allTab.created_by + " on " + formatDate(allTab.created_at)}
    </span>
    <div className="ml-auto">
      {savedTabs.some(
        (savedTab) => savedTab.tabs.id === allTab.id
      ) ? (
        <Image
          src="/saved.png"
          alt="saved tab"
          className="dark:invert"
          width={24}
          height={24}
          onClick={() => {
            toggleSaved(userId, allTab);
          }}
        />
      ) : (
        <Image
          src="/not_saved.png"
          alt="unsaved tab"
          className="dark:invert"
          width={24}
          height={24}
          onClick={() => {
            toggleSaved(userId, allTab);
          }}
          priority
        />
      )}
    </div>
  </React.Fragment>
))}
html reactjs node.js react-hooks drop-down-menu
1个回答
0
投票

看来您没有意识到

.sort
应用了就地突变。如果
allTabs
是某种 React 状态,那么改变状态不会触发组件重新渲染。我怀疑这就是为什么您在效果中看到正确的排序数组突变和日志,但组件没有被触发重新渲染以显示新的排序数组。
useEffect
回调也会在渲染周期结束时调用,因此即使突变可见,也已经为时已晚,渲染周期已完成。

一般来说,在对数组数据进行排序或过滤时,常见的模式是对源数组进行过滤/排序,例如事实的状态来源,而是使用源数组和对其应用的过滤/排序标准来计算派生的过滤/排序值。

因为

.sort
应用就地排序/突变,您需要首先浅复制
allTabs
数组:

  • 使用
    .slice
    创建副本:
    allTabs.slice().sort(compareFn)
  • 使用扩展语法复制到新的数组引用中:
    [...allTabs].sort(compareFn)
  • 如果您的系统支持,请使用较新的
    .toSorted
    方法:
    allTabs.toSorted(compareFn)

示例:

const compareFn = (sortOption: string) => (a: Tab, b: Tab) => {
  switch (sortOption) {
    case "Newest to Oldest":
    default:
      return compareDates(a, b);

    case "Oldest to Newest":
      return compareDates(b, a));

    case "A-Z":
      return a.name.localeCompare(b.name);

    case "Z-A":
      return b.name.localeCompare(a.name);
  }
}
const [sortOption, setSortOption] = useState<string>("Newest to Oldest");
const [allTabs, setAllTabs] = useState<Tab[]>([......]);

const sortedAllTabs = allTabs.slice().sort(compareFn(sortOption));
// const sortedAllTabs = [...allTabs].sort(compareFn(sortOption));
// const sortedAllTabs = allTabs.toSorted(compareFn(sortOption));

如果您愿意,您可以额外记住计算/导出的值作为性能优化的一点。使用

useMemo
钩子仅在任何依赖项更新时重新计算值,而不是在渲染时调用组件主体时重新计算值。

const sortedAllTabs = useMemo(() => {
  return allTabs.slice().sort(compareFn(sortOption));
  // return [...allTabs].sort(compareFn(sortOption));
  // return allTabs.toSorted(compareFn(sortOption));
}, [allTabs, sortOption]);

使用计算/派生的

sortedAllTabs
数组映射到渲染的 JSX。另请注意,由于您正在改变数组顺序,因此使用数组索引作为 React 键的效果非常差,因为数组索引不会“粘住”它们应该表示的数据。您应该使用数据固有的且唯一的值/属性。 Id 属性或兄弟元素中唯一的任何属性就足够了。在您的代码中,似乎
allTab.id
可能合适。

{sortedAllTabs.map((allTab) => (
  <React.Fragment key={allTab.id}>
    <Link
      className=""
      href="/"
      onClick={() => {
        localStorage.setItem("tabTitle", allTab.name);
        localStorage.setItem("tabContent", allTab.tab);
      }}
    >
      {allTab.name}
    </Link>
    <span>
      {allTab.created_by + " on " + formatDate(allTab.created_at)}
    </span>
    <div className="ml-auto">
      {savedTabs.some(
        (savedTab) => savedTab.tabs.id === allTab.id
      ) ? (
        <Image
          src="/saved.png"
          alt="saved tab"
          className="dark:invert"
          width={24}
          height={24}
          onClick={() => {
            toggleSaved(userId, allTab);
          }}
        />
      ) : (
        <Image
          src="/not_saved.png"
          alt="unsaved tab"
          className="dark:invert"
          width={24}
          height={24}
          onClick={() => {
            toggleSaved(userId, allTab);
          }}
          priority
        />
      )}
    </div>
  </React.Fragment>
))}
© www.soinside.com 2019 - 2024. All rights reserved.