useEffect挂钩与setTimeout和状态不符

时间:2019-12-06 09:35:41

标签: javascript reactjs react-hooks toast

我在练习React应用程序中创建了一个自定义的Toast组件。在尝试引入自动关闭超时功能之前,它一直工作正常。基本上,当您加载新的吐司时,需要在说5000毫秒后将其关闭。

如果要检查我的Github Repo中的完整代码,其中也包含一个live preview

创建吐司的最简单方法是输入无效的邮件/密码。

我相信我在useEffect钩子上做错了什么,或者我错过了一些事情。问题是当我创建多个吐司时,它们会同时消失。 React也抱怨我没有将remove作为useEffect钩子的依赖项,但是当我这样做时,情况变得更糟。有人可以揭开这件事发生的原因以及如何解决之谜。我对React有点陌生。

以下是围绕我的主要App组件创建HOC的文件:

import React, { useState } from 'react';
import { createPortal } from 'react-dom';

import ToastContext from './context';
import Toast from './Toast';
import styles from './styles.module.css';

function generateUEID() {
  let first = (Math.random() * 46656) | 0;
  let second = (Math.random() * 46656) | 0;
  first = ('000' + first.toString(36)).slice(-3);
  second = ('000' + second.toString(36)).slice(-3);

  return first + second;
}

function withToastProvider(Component) {
  function WithToastProvider(props) {
    const [toasts, setToasts] = useState([]);
    const add = (content, type = 'success') => {
      const id = generateUEID();

      if (toasts.length > 4) {
        toasts.shift();
      }

      setToasts([...toasts, { id, content, type }]);
    };
    const remove = id => {
      setToasts(toasts.filter(t => t.id !== id));
    };

    return (
      <ToastContext.Provider value={{ add, remove, toasts }}>
        <Component {...props} />

        { createPortal(
            <div className={styles.toastsContainer}>
              { toasts.map(t => (
                  <Toast key={t.id} remove={() => remove(t.id)} type={t.type}>
                    {t.content}
                  </Toast>
              )) }
            </div>,
            document.body
        ) }
      </ToastContext.Provider>
    );
  }

  return WithToastProvider;
}

export default withToastProvider;

还有Toast组件:

import React, { useEffect } from 'react';
import styles from './styles.module.css';

function Toast({ children, remove, type }) {
  useEffect(() => {
    const duration = 5000;
    const id = setTimeout(() => remove(), duration);
    console.log(id);

    return () => clearTimeout(id);
  }, []);

  return (
    <div onClick={remove} className={styles[`${type}Toast`]}>
      <div className={styles.text}>
        <strong className={styles[type]}>{type === 'error' ? '[Error] ' : '[Success] '}</strong>
        { children }
      </div>
      <div>
        <button className={styles.closeButton}>x</button>
      </div>
    </div>
  );
}

export default Toast;

1 个答案:

答案 0 :(得分:1)

今天在搜索解决方案时,我发现它here

您将需要使用useRef及其current属性

这是我将Toast组件转换为工作方式的方法:

import React, { useEffect, useRef } from 'react';
import styles from './styles.module.css';

function Toast({ children, remove, type }) {
  const animationProps = useSpring({opacity: .9, from: {opacity: 0}});
  const removeRef = useRef(remove);
  removeRef.current = remove;

  useEffect(() => {
    const duration = 5000;
    const id = setTimeout(() => removeRef.current(), duration);

    return () => clearTimeout(id);
  }, []);

  return (
    <div onClick={remove} className={styles[`${type}Toast`]}>
      <div className={styles.text}>
        <strong className={styles[type]}>{type === 'error' ? '[Error] ' : '[Success] '}</strong>
        { children }
      </div>
      <div>
        <button className={styles.closeButton}>x</button>
      </div>
    </div>
  );
}

export default Toast;