简体   繁体   English

useEffect 钩子在 setTimeout 和 state 中行为不当

[英]useEffect hook misbehaves with setTimeout and state

I created a custom toast component in my exercise React application.我在练习 React 应用程序中创建了一个自定义 Toast 组件。 It is working correctly until the moment I try to introduce an auto dismiss timeout functionality.在我尝试引入自动关闭超时功能之前,它一直工作正常。 Basically when you load a new toast it needs to dismiss itself after let say 5000ms.基本上,当你加载一个新的 toast 时,它需要在 5000 毫秒后自行关闭。

If you want check the full code in my Github Repo that also have a live preview . 如果您想查看我的Github Repo中的完整代码,该代码也有实时预览

Easiest way to create toast is put invalid mail / password.创建 toast 的最简单方法是放置无效的邮件/密码。

I believe I am doing something wrong with the useEffect hook or I am missing something.我相信我在useEffect钩子上做错了什么,或者我遗漏了一些东西。 The problem is that when I am creating multiple toasts they disappear all at the same time.问题是,当我创建多个 toast 时,它们会同时消失。 Also React is complaining that I didn't include remove as a dependency of the useEffect hook but when I do it becomes even worse. React 还抱怨我没有将remove作为useEffect钩子的依赖项包含useEffect但是当我这样做时,情况变得更糟。 Can someone demystify why this is happening and how it can be fixed.有人可以揭开为什么会发生这种情况以及如何解决它的神秘面纱。 I am a bit new to React.我对 React 有点陌生。

Here is the file that creates a HOC around my main App component:这是围绕我的主要 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;

And the Toast component:和 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;

Searching today for the solution I found it here今天搜索我在这里找到的解决方案

You will need to use useRef and its current property您将需要使用useRef及其current属性

Here is how I transformed the Toast component to work:以下是我如何将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;

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM