将React Context作为useEffect依赖项数组的一部分用于简单的Toast通知系统

时间:2019-12-20 13:14:12

标签: javascript reactjs react-hooks

我正在使用React Context构建一个简单的Toast通知系统。这是一个简化但可以正常运行的示例的链接,该示例显示了问题https://codesandbox.io/s/currying-dust-kw00n

我的页面组件包装在HOC中,使我能够以编程方式在此页面内添加,删除和删除所有Toast。该演示有一个按钮,用于添加祝酒通知和一个按钮,用于更改activeStep(假设这是一个多步骤表单)。当activeStep更改时,我希望所有烤面包都被移除。

最初,我是使用以下方法完成的...

useEffect(() => {
  toastManager.removeAll();
}, [activeStep]);

...这按我的预期工作,但是有一个react-hooks / exhaustive-deps ESLint警告,因为toastManager不在依赖项数组中。将toastManager添加到数组中后,会导致敬酒一经添加就被删除。

我以为我可以使用useCallback ...

const stableToastManager = useCallback(toastManager, []);
useEffect(() => {
  stableToastManager.removeAll();
}, [activeStep, stableToastManager]);

...但是,这不仅行不通,而且我希望从源头上解决该问题,所以我不需要每次都想要这种功能时就这样做,因为它很可能会用于很多地方。

这就是我被困住的地方。我不确定如何更改上下文,这样就不需要在HOC封装的组件中添加其他逻辑。

export const ToastProvider = ({ children }) => {
  const [toasts, setToasts] = useState([]);

  const add = (content, options) => {
    // We use the content as the id as it prevents the same toast
    // being added multiple times
    const toast = { content, id: content, ...options };
    setToasts([...toasts, toast]);
  };

  const remove = id => {
    const newToasts = toasts.filter(t => t.id !== id);
    setToasts(newToasts);
  };

  const removeAll = () => {
    if (toasts.length > 0) {
      setToasts([]);
    }
  };

  return (
    <ToastContext.Provider value={{ add, remove, removeAll }}>
      {children}

      <div
        style={{
          position: `fixed`,
          top: `10px`,
          right: `10px`,
          display: `flex`,
          flexDirection: `column`
        }}
      >
        {toasts.map(({ content, id, ...rest }) => {
          return (
            <button onClick={() => remove(id)} {...rest}>
              {content}
            </button>
          );
        })}
      </div>
    </ToastContext.Provider>
  );
};

export const withToastManager = Component => props => {
  return (
    <ToastContext.Consumer>
      {context => {
        return <Component toastManager={context} {...props} />;
      }}
    </ToastContext.Consumer>
  );
};

1 个答案:

答案 0 :(得分:1)

如果要“从核心修复”,则需要修复ToastProvider

const add = useCallback((content, options) => {
  const toast = { content, id: content, ...options };
  setToasts(pToasts => [...pToasts, toast]);
}, []);

const remove = useCallback(id => {
  setToasts(p => p.filter(t => t.id !== id));
}, []);

const removeAll = useCallback(() => {
  setToasts(p => (p.length > 0 ? [] : p));
}, []);


const store = useMemo(() => ({ add, remove, removeAll }), [
  add,
  remove,
  removeAll
]);

然后,useEffect将按预期工作,因为问题在于您需要在每个渲染中将ToastProvider功能重新初始化为单例时。

useEffect(() => {
  toastManager.removeAll();
}, [activeStep, toastManager]);

此外,我建议添加自定义钩子功能作为默认用例,并仅为类组件提供包装器。

换句话说,请勿在功能组件上使用包装器(withToastManager),将其用于类,因为它被认为是反模式,因此您得到了useContext,因此您的库应暴露它。

// @ toastContext.js
export const useToast = () => {
  const context = useContext(ToastContext);
  return context;
};

// @ page.js
import React, { useState, useEffect } from 'react';
import { useToast } from './toastContext';

const Page = () => {
  const [activeStep, setActiveStep] = useState(1);
  const { removeAll, add } = useToast();

  useEffect(() => {
    removeAll();
  }, [activeStep, removeAll]);

  return (
    <div>
      <h1>Page {activeStep}</h1>

      <button
        onClick={() => {
          add(`Toast at ${Date.now()}!`);
        }}
      >
        Add Toast
      </button>

      <button
        onClick={() => {
          setActiveStep(activeStep + 1);
        }}
      >
        Change Step
      </button>
    </div>
  );
};

export default Page;

Edit intelligent-lake-2j51q