[英]can't clean up setTimeout in useEffect unmount cleanup
我正在尝试制作一种 flash 消息显示器,以在一定时间内在顶部显示成功、错误、警告消息。
我已经使用 useRef 挂钩来存储超时,以便在超时完成之前卸载组件时清除它。
一切都按预期工作,除非组件在超时回调之前卸载,它不会清除确实正在尝试 setState 的超时
警告:无法在未安装的组件上执行 React state 更新
import React, { useEffect, useRef, useState } from 'react' import SuccessGreen from '../../assets/SuccessGreen.svg' import Cross from '../../assets/Cancel.svg' import WarningExclamation from '../../assets/WarningExclamation.svg' const ICONS_MAP = { "warning": WarningExclamation, "success": SuccessGreen, "error": "" } export const FlashMessages = ({ duration=5000, closeCallback, pauseOnHover=false, messageTheme='warning', typoGraphy={className: 'text_body'}, firstIcon=true, ...props }) => { const [isDisplayable, setIsDisplayable] = useState(true) const resumedAt = useRef(null) const remainingDuration = useRef(duration) const countDownTimer = useRef(null) useEffect(() => { countDownTimer.current = resumeDuration() console.log(countDownTimer, "From mount") return () => {clearTimeout(countDownTimer.current)} }, []) const resumeDuration = () => { clearTimeout(countDownTimer.current) resumedAt.current = new Date() return setTimeout(() => forceCancel(), remainingDuration.current) } const pauseDuration = () => { if(pauseOnHover){ clearTimeout(countDownTimer.current) remainingDuration.current = remainingDuration.current - (new Date() - resumedAt.current) } } const forceCancel = () => { console.log(countDownTimer, "From force") clearTimeout(countDownTimer.current); setIsDisplayable(false); closeCallback(null); } return isDisplayable? ( <div onMouseEnter={pauseDuration} onMouseLeave={resumeDuration} className={`flash_message_container ${messageTheme} ${typoGraphy.className}`} style={props.style}> { firstIcon? (<img src={ICONS_MAP[messageTheme]} style={{marginRight: 8, width: 20}} />): null } <div style={{marginRight: 8}}>{props.children}</div> <img src={Cross} onClick={forceCancel} style={{cursor: 'pointer', width: 20}}/> </div> ):null }
我试图模仿这个 npm package https://github.com/danielsneijers/whiact-flash/index.jsxage/
我认为问题在于当mouseleave
事件发生时, resumeDuration
返回的超时 id 没有保存在countDownTimer.current
中,因此在 useEffect 返回的清理useEffect
中没有清除超时。
您可以修改resumeDuration
以将超时 id 保存到countDownTimer.current
而不是返回它:
countDownTimer.current = setTimeout(() => forceCancel(), remainingDuration.current)
然后,在useEffect
中,只需调用resumeDuration
,因此组件将如下所示:
import React, { useEffect, useRef, useState } from 'react'
import SuccessGreen from '../../assets/SuccessGreen.svg'
import Cross from '../../assets/Cancel.svg'
import WarningExclamation from '../../assets/WarningExclamation.svg'
const ICONS_MAP = {
"warning": WarningExclamation,
"success": SuccessGreen,
"error": ""
}
export const FlashMessages = ({
duration=5000,
closeCallback,
pauseOnHover=false,
messageTheme='warning',
typoGraphy={className: 'text_body'},
firstIcon=true,
...props
}) => {
const [isDisplayable, setIsDisplayable] = useState(true)
const resumedAt = useRef(null)
const remainingDuration = useRef(duration)
const countDownTimer = useRef(null)
useEffect(() => {
resumeDuration()
console.log(countDownTimer, "From mount")
return () => {clearTimeout(countDownTimer.current)}
}, [])
const resumeDuration = () => {
clearTimeout(countDownTimer.current)
resumedAt.current = new Date()
countDownTimer.current = setTimeout(() => forceCancel(), remainingDuration.current)
}
const pauseDuration = () => {
if(pauseOnHover){
clearTimeout(countDownTimer.current)
remainingDuration.current = remainingDuration.current - (new Date() - resumedAt.current)
}
}
const forceCancel = () => {
console.log(countDownTimer, "From force")
clearTimeout(countDownTimer.current);
setIsDisplayable(false);
closeCallback(null);
}
return isDisplayable ? (
<div onMouseEnter={pauseDuration} onMouseLeave={resumeDuration}
className={`flash_message_container ${messageTheme} ${typoGraphy.className}`} style={props.style}>
{ firstIcon ? (<img src={ICONS_MAP[messageTheme]} style={{marginRight: 8, width: 20}} />) : null }
<div style={{marginRight: 8}}>{props.children}</div>
<img src={Cross} onClick={forceCancel} style={{cursor: 'pointer', width: 20}}/>
</div>
):null
}
然后它将模仿https://github.com/danielsneijers/react-flash-message/blob/master/src/index.jsx的逻辑
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.