反应上下文(钩子)不更新所有引用

Posted

技术标签:

【中文标题】反应上下文(钩子)不更新所有引用【英文标题】:React context (hooks) not updating all references 【发布时间】:2021-10-26 23:33:31 【问题描述】:

我正在尝试使用自定义关闭回调实现模式。我使用一个 useState 挂钩来完成此操作,该挂钩存储此方法并在已定义的 closeModal() 中执行它。 我的问题是,当我尝试从上下文中使用 closeModal 时,回调会保留初始值,而不是更新后的值。

function ModalProvider( children ) 
  const [component, setComponent] = useState<React.ReactNode>(null);
  const [styles, setStyles] = useState<React.CSSProperties>(null);
  const [onClose, setOnClose] = useState<() => void>(null);

  const openModal = (
    component,
    containerStyles,
    onClose,
  : OpenModalProps) => 
    setComponent(component);
    setStyles(containerStyles);
    setOnClose(() => onClose);
  ;

  const closeModal = () => 
    onClose?.();

    setComponent(null);
    setStyles(null);
    setOnClose(null);
  ;

  return (
    <ModalContext.Provider value= component, styles, openModal, closeModal >
      children
    </ModalContext.Provider>
  );

所以这里我分两点使用,按钮和自定义钩子useClickOutside。当我单击按钮时,它可以正常工作,但是当从钩子中捕获事件时,会发生模式关闭但 onClose 未执行,因为它为空。

function Modal() 
  const  component, styles, closeModal  = useContext(ModalContext);
  const contentRef = useRef<htmlDivElement>(null);

  const [stopScrolling, continueScrolling] = useStopScrolling();

  useClickOutside(contentRef, closeModal);
  useEffect(() => 
    if (!component) continueScrolling();
    if (component) stopScrolling();
  , [component]);

  if (!component) return <></>;

  return (
    <>
      <S.ModalWrapper>
        <section style=styles ref=contentRef>
          <button onClick=closeModal>x</button>
          component
        </section>
      </S.ModalWrapper>
    </>
  );

function useClickOutside(ref, onClickOutside: () => void) 
  const handleClickOutside = (event) => 
    if (ref.current && !ref.current.contains(event.target)) 
      onClickOutside();
    
  ;

  useEffect(() => 
    document.addEventListener("mousedown", handleClickOutside);

    return () => 
      window.removeEventListener("mousedown", handleClickOutside);
    ;
  , [ref]);

我在其他应用点上使用此方法也失败了,但我认为这足以发现问题。谢谢。

编辑

这就是我调用模态的方式

      openModal(
        component: <QuickView product=product close=closeModal />,
        containerStyles: 
          width: "80%",
          maxWidth: "1000px",
          height: "auto",
          backgroundColor: "transparent",
        ,
        onClose: () => 
          const as = router.asPath;
          router.push(as, as,  shallow: true );
        ,
      );

【问题讨论】:

请注意,您传递给openModalcomponent 也将包含陈旧的closeModal 【参考方案1】:

由于您的代码中有多个位置closeModal 在闭包中被捕获而没有被声明为依赖项,因此它有可能在变得陈旧后被调用。

在这种情况下,确保它永远不会有对onClose 的过时引用会更容易,您可以通过使用useRef() 而不是useState() 来定义它来做到这一点:

  const [component, setComponent] = useState<React.ReactNode>(null);
  const [styles, setStyles] = useState<React.CSSProperties>(null);
  // const [onClose, setOnClose] = useState<() => void>(null);
  const onCloseRef = useRef<() => void>(null);

  const openModal = (
    component,
    containerStyles,
    onClose,
  : OpenModalProps) => 
    setComponent(component);
    setStyles(containerStyles);
    // setOnClose(() => onClose);
    onCloseRef.current = onClose;
  ;

  const closeModal = () => 
    // onClose?.();
    onCloseRef.current?.();

    setComponent(null);
    setStyles(null);
    // setOnClose(null);
    onCloseRef.current = null;
  ;

【讨论】:

@MiguelRodriguez 您能否编辑您的问题并提供从上下文中使用并调用openModal 的组件的代码? @MiguelRodriguez 我强烈建议使用eslint-plugin-react-hooks linting 以自动检测此类过时的关闭问题。 我只是在调用 openModal 的地方添加代码

以上是关于反应上下文(钩子)不更新所有引用的主要内容,如果未能解决你的问题,请参考以下文章

反应钩子:useState/context;无法读取未定义的属性“头像”/如何更新嵌套对象

如何将 React 钩子(useContext、useEffect)与 Apollo 反应钩子(useQuery)结合起来

是否可以通过反应(钩子)在另一个上下文中使用上下文?

反应:无法使用 useContext 钩子在 app.js 中设置上下文状态

反应 useMemo 钩子用例

使用Hook更新上下文状态值