【问题标题】:Why useState provided state is not getting updated in some particular scenarios?为什么 useState 提供的状态在某些特定情况下没有得到更新?
【发布时间】:2022-11-27 10:03:36
【问题描述】:

我在 StackOverflow 上经历过许多类似的 QnA,但我仍然对为什么它对我不起作用感到困惑。我知道状态更新不是同步的。我也没有执行任何 DOM 操作。

完整的工作演示在这里演示了这个问题 - https://codesandbox.io/s/falling-pine-kljujf?file=/src/Tabs.js

在这个例子中,我渲染了几个标签。一个新选项卡最初会显示 DB“表”名称列表(此演示已修复)。选择表格名称时,列表将替换为表格的内容。

问题是当您关闭打开的选项卡时,currentTab 状态不会更新到我设置的(打开)选项卡。因此,在我手动单击打开的选项卡名称之前,选项卡的详细信息区域保持空白。

在上图中,我关闭了第三个选项卡。期望选项卡选择应该自动更改为第二个选项卡,但事实并非如此。相同的代码如下。

  function removeTab(id) {
    const ntl = tabsList;
    const idx = ntl.findIndex((v) => v.id === id);
    if (idx !== -1) {
      ntl.splice(idx, 1);
      if (ntl.length) {
        let t = ntl[idx];
        console.log("------", t, idx);
        if (!t) {
          t = ntl[0];
        }
        console.log("++++++1", t, t.id);
        setCurrentTab(t.id);
        setTabsList([...ntl]);
      } else {
        const t = newTab();
        console.log("++++++2", t, t.id);
        setCurrentTab(t.id);
        setTabsList([t]);
      }
    }
  }

在传递的id 上方是第三个选项卡。 tabsList 状态包含一个包含每个选项卡数据的数组。 currentTab 仅包含当前选项卡的id。根据上面的 console.log 语句,传递了正确选项卡的 id,但 currentTab 永远不会更新。即使我输入如下代码。

useEffect(() => { console.log('------------>', currentTab) }, [currentTab]);

在这种情况下它永远不会触发。

removeTab 方法是从 JSX 调用的,如下所示。

{tabsList.map((t) => (
          <a
            key={t.id + ""}
            className={
              "tab tab-bordered " + (currentTab === t.id ? "tab-active" : "")
            }
            onClick={() => {
              setCurrentTab(t.id);
            }}
          >
            {t.name}
            <button
              onClick={() => {
                removeTab(t.id); // On clicking X button we remove tab
              }}
            >
              X
            </button>
          </a>
        ))}

【问题讨论】:

    标签: javascript reactjs react-hooks


    【解决方案1】:

    有两个问题

    • 拼接是直接变异状态,为了防止这种情况,您可以使用扩展运算符来制作数组的副本。
    • 其次,Button在Anchor Tag里面并且都有onClick,所以当十字按钮被点击时,Parent anchor onclick也会被调用,为了防止这种情况,你可以做e.stopPropagation()。

    这是我对 codesandbox 所做的编辑 https://codesandbox.io/s/practical-platform-dvkxrq?file=/src/Tabs.js:3723-3738

    【讨论】:

    • 你是我的英雄!在我的初始版本中,我确实在变异之前创建了一个数组副本,但问题仍然存在。主要问题是点击事件气泡。它完全逃过了我的眼睛。有时问题远比我们想象的要简单。
    • @AppleGrew 谢谢。乐意效劳!
    【解决方案2】:

    我对代码做了一些更改,它们可能会帮助您找到更好的解决方案

    在 state 中设置初始值选项卡而不是 useEffect,to reduce a render cycles

    Creating a new array 删除和添加

    你可以找到modified codesandbox example

    使用单个选中项渲染内容to prevent duplication

    唯一的问题是 React 没有刷新删除时的更改,所以我不得不使用 hack,在这种情况下你可能会发现它有帮助

    let tabId = 1;
    
    const DisplayTable = ({ isNewMode, name, ...rest }) => {
      return (
        <div>
          {isNewMode ? (
            <TableSelection {...rest} />
          ) : (
            <div>Contents of table: {name}</div>
          )}
        </div>
      );
    };
    
    export default function Tabs() {
      const [tabsList, setTabsList] = useState([
        {
          id: tabId,
          name: "Select table",
          isNewMode: true
        }
      ]);
      const [currentTab, setCurrentTab] = useState(tabId);
    
      function newTab() {
        tabId = tabId + 1; // unique id check
        return {
          id: tabId,
          name: "Select table",
          isNewMode: true
        };
      }
    
      function addTab() {
        const t = newTab();
        setTabsList((tabs) => [...tabs, t]);
        console.log("++++++3", t, t.id);
        setCurrentTab(t.id);
      }
    
      async function removeTab(id) {
    
        //  functional updaters are simpler
        //  setTabsList(tabs => [...tabs.filter((a) => a.id !== id)])
    
    
        // need better logic to select the right tab, this seems simeple enough to understand
        const deleteIndex = tabsList.findIndex((v) => v.id === id);
        // 
        if (deleteIndex !== -1) {
          const filteredItems = tabsList.filter((a) => a.id !== id);
    
          if (deleteIndex > 0) {
            const prevId = filteredItems[deleteIndex - 1];
            console.log("prevId", prevId);
    
            setTabsList([...filteredItems]);
            await Promise.resolve();
            setCurrentTab(prevId.id);
          }
        }
      }
    
      const selectedItem = tabsList?.find((item) => item.id === currentTab);
    
      // console.log("Rendering......", currentTab, tabsList);
      return (
        <div className="p-8 h-full flex flex-col">
          <div className="tabs">
            {tabsList.map((t) => (
              <a
                href="#"
                key={t.id}
                className={
                  "tab tab-bordered " + (currentTab === t.id ? "tab-active" : "")
                }
                onClick={(e) => {
                  e.preventDefault();
                  setCurrentTab(t.id);
                }}
              >
                {t.name}
                <button
                  className="ml-2 btn btn-ghost btn-square btn-xs"
                  onClick={(e) => {
                    e.preventDefault();
                    removeTab(t.id);
                  }}
                >
                  X
                </button>
              </a>
            ))}
            <a href="#" className="tab">
              <button
                className="btn btn-ghost btn-square btn-xs"
                onClick={(e) => {
                  e.preventDefault();
                  addTab();
                }}
              >
                +
              </button>
            </a>
          </div>
          {selectedItem ? (
            <DisplayTable
              onTableSelection={(item) => {
                tabId = tabId + 1;
                const d = { ...item, id: tabId, isNewMode: false };
                setTabsList((t) => [...t, d]);
                console.log("table select", d);
                setCurrentTab(d.id);
              }}
              {...selectedItem}
            />
          ) : null}
          {/* {tabsList.map((t) => (
            <div key={t.id}>
              <TableSelection />
            </div>
          ))} */}
        </div>
      );
    }
    

    希望在某种程度上有所帮助,

    干杯

    【讨论】:

      猜你喜欢
      • 2013-12-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-08-07
      • 2019-12-27
      • 2021-08-04
      相关资源
      最近更新 更多