使用自定义钩子事件处理来响应陈旧状态

时间:2021-03-28 13:04:47

标签: javascript reactjs typescript react-hooks state

我似乎无法解决 React 中过时的状态问题,因为它与事件处理程序和钩子有关。我从概念上理解发生了什么 - 有一个闭包捕获状态值的起始值,并且在我期望的时候没有更新。

我正在创建一个 NavBar 组件,我想在该组件上添加键盘控件以允许访问子菜单等。我将展示代码,然后描述正在发生/未发生的事情。我还将链接到一个代码沙盒,以便于分叉和调试。

CodeSandbox

导航栏

const NavBar: React.FC<Props> = ({ children, label }) => {
  const {
    actions: { createNavItemRef },
    state: { activeSubMenuIndex, navBarRef },
  } = useNav();

  console.log('NAV.BAR', { activeSubMenuIndex });

  return (
    <nav aria-label={label} ref={navBarRef}>
      <NavList aria-label={label} role="menubar">
        {children} // NavItems
      </NavList>
    </nav>
  );
};

导航项

const NavItem: React.FC<Props> = ({ children, hasSubMenu, to }) => {
  const ChildrenArray = React.Children.toArray(children);
  const {
    actions: { handleSelectSubMenu },
    state: { activeSubMenuIndex },
  } = useNav();

  const handleSubMenuToggle = () => {
    handleSelectSubMenu(index);
  };

  return (
    <li ref={ref} role="none">
      <>
        <ParentButton
          aria-expanded={activeSubMenuIndex === index}
          aria-haspopup="true"
          onClick={handleSubMenuToggle}
          role="menuitem"
          tabIndex={index === 0 ? 0 : -1}
        >
         {ChildrenArray.shift()}
        </ParentButton>
        {ChildrenArray.pop()}
      </>
    </li>
  );
};

使用导航

function useNav() {
  const navBarRef = useRef<HTMLUListElement>(null);    
  const [activeSubMenuIndex, setActiveSubMenuIndex] = useState<number | undefined>();

  const handleSelectSubMenu = (index?: number) => {
    if (!index || activeSubMenuIndex === index) {
      setActiveSubMenuIndex(undefined);
    } else {
      setActiveSubMenuIndex(index);
    }
  };

  useEffect(() => {
    const navbar = navBarRef?.current;

    navbar?.addEventListener('keydown', () => {
      console.log("UseNav", { activeSubMenuIndex });
    });

    // return () => remove event listener
  }, [navBarRef, activeSubMenuIndex]);

  return {
    actions: {
      createNavItemRef,
      handleSelectSubMenu,
    },
    state: {
      activeSubMenuIndex,
      navBarRef,
    },
  };
}

这是我设置的精简版。最终,这就是正在发生的事情。

期待 我点击第一个 NavItem 并且它变成焦点。我按了一个箭头键(例如),日志 UseNav { activeSubMenuIndex })undefined 正确注销。

然后我单击包含子菜单的 NavItem。 activeSubMenuIndex 更新并在 NavItem 中显示正确的子菜单(基于 activeSubMenuIndex === index 条件)。

但是,我希望 NavBar { activeSubMenuIndex }) 在单击此 NavItem 时也能注销。但它没有。

在子菜单可见的情况下,我按下另一个箭头键,当 UseNav 日志显示时,我希望它包含正确的 activeSubMenuIndex 值,但它仍然是 undefined .

最终,我需要在 NavBar 上为 keyPress 添加事件监听器,以便在整个过程中分配键盘导航。但是,如果我什至无法在这个 MVP 级别上正确更新状态值,那么如果不让它在以后使用和调试变得更加麻烦,我就无法真正向前推进。

我知道这是一个陈旧状态的问题,但我找不到任何关于这个主题的好文章,而不仅仅是在同一文件中增加一个数字。因此,任何有助于最终突破这堵墙的帮助都将是惊人的。

谢谢!

1 个答案:

答案 0 :(得分:0)

看起来这个问题源于 useNav() 钩子的使用。当我在 NavBar 内部调用这个钩子时,引用和值被实例化一次。当我在 NavItem 中再次调用钩子时,这些相同的引用和值将再次实例化。

在这种情况下,与其使用钩子,不如将其包装在上下文中,以便将逻辑与 UI 隔离开来,但使组件在其数据源中保持一致。