为什么我的React应用程序中的后续警报显示给定会话中显示的第一个警报的“ useCountdown挂钩计时器”?

克拉克·道丁

我的橡皮鸭没有给我答案。他通常更聪明。

在我的Create-React-App中,我有一个警报上下文和一个react组件,用于处理整个应用中的弹出警报。我是第一次玩React Hooks,并尝试在每个Alert上设置一个倒数计时器。

我编写了一个useCountdown钩子(如下所示的代码),该钩子在其他测试组件上也可以正常工作,但是当我尝试将其与警报一起使用时,给定会话中的每个警报都使用触发的第一个警报中的计时器值。

在此错误警报的屏幕快照中,显示的每个警报具有非常不同的倒计时值,但是它们都显示了第一个警报的倒计时值。倒数计时在右上角。

我已经使用过useEffect()来尝试解决此问题,但很明显,我还有很多关于钩子及其作用域的知识要学习。

任何建议将不胜感激。最终,我将计时器设置为一个小的手动关闭按钮,并带有秒数,直到自动关闭显示为按钮的文本。现在...只是在丑陋的模式。

useCountdown挂钩

import { useState, useEffect } from 'react'

const useCountdown = (m = 1, s = 10) => {
  const [minutes, setMinutes] = useState(m)
  const [seconds, setSeconds] = useState(s)

  useEffect(() => {
    let myInterval = setInterval(() => {
      if (seconds > 0) {
        setSeconds(seconds - 1)
      }
      if (seconds === 0) {
        if (minutes === 0) {
          clearInterval(myInterval)
        } else {
          setMinutes(minutes - 1)
          setSeconds(59)
        }
      }
    }, 1000)
    return () => {
      clearInterval(myInterval)
    }
  })

  const finalSeconds = seconds < 10 ? `0${seconds}` : `${seconds}`

  return `${minutes}:${finalSeconds}`
}

export default useCountdown

警报组件

import React, { useContext, Fragment } from 'react'
import AlertContext from '../../context/alert/alertContext'
import { CSSTransition, TransitionGroup } from 'react-transition-group'
import styled from 'styled-components' // TODO Refactor to styled

// Custom hook to give each Alert a countdown timer
import useCountdown from '../../hooks/useCountdown' // BUG!

const Alerts = () => {
  // const testAlert = {
  //   id: 12345,
  //   title: 'Problem',
  //   color: 'alert-danger',
  //   icon: 'fas fa-exclamation-triangle',
  //   seconds: 20,
  //   msg: ['This is an alert. Testing 123.', 'Auto-dismiss in 20 seconds.'],
  // }

  const alertContext = useContext(AlertContext)
  const { removeAlert } = alertContext

  // ISSUE #32 The initial value gets used through-out session
  const countdown = useCountdown(alert.seconds)

  return (
    <Fragment>
      <TransitionGroup>
        {alertContext.alerts.length > 0 &&
          alertContext.alerts.map((alert) => (
            <CSSTransition key={alert.id} timeout={500} classNames='pop'>
              <div className={`alert ${alert.color}`}>
                <div className='alert-title'>
                  <h1 onClick={() => removeAlert(alert.id)}>
                    <i className={`${alert.icon}`} /> <span className='hide-sm'>{alert.title}</span>
                  </h1>
                  <span className='alert-countdown'>{countdown}</span>
                  <button className='btn btn-link btn-sm alert-btn hide-sm' onClick={() => removeAlert(alert.id)}>
                    <i className='far fa-times-square fa-3x' />
                  </button>
                </div>
                <div className='alert-items'>
                  <ul>
                    {alert.msg.map((item) => (
                      <li key={item}>
                        <i className='fas fa-chevron-circle-right'></i> <span>{item}</span>
                      </li>
                    ))}
                  </ul>
                </div>
              </div>
            </CSSTransition>
          ))}
      </TransitionGroup>
    </Fragment>
  )
}

export default Alerts

朱莲

问题是您useCountdownAlerts 容器组件中使用了自己因此为所有子元素存储了一个状态。基本上,当前您只有一个时间间隔,因此有一个用于所有警报的倒计时值。

如果希望每个警报具有单独的状态(倒计时值),则应将警报UI提取到单独的组件中并在其中使用useCountdown钩子。然后,重构Alerts容器Alert组件以为每个实例呈现一个组件。

这是示例代码。它可能包含语法错误,因为我现在无法测试,但可以尝试:

警报容器组件:

import React, { useContext, Fragment } from 'react'
import AlertContext from '../../context/alert/alertContext'
import { CSSTransition, TransitionGroup } from 'react-transition-group'
import styled from 'styled-components' // TODO Refactor to styled

const Alerts = () => {
  const alertContext = useContext(AlertContext)
  const { removeAlert } = alertContext

  return (
    <Fragment>
      <TransitionGroup>
        {alertContext.alerts.length > 0 &&
          alertContext.alerts.map((alertInstance) => (
              <CSSTransition key={alertInstance.id} timeout={500} classNames='pop'>
                <Alert alert={alertInstance} removeAlert={removeAlert}/>
              </CSSTransition>
          ))}
      </TransitionGroup>
    </Fragment>
  )
}

export default Alerts

警报组件:

export function Alert(props) {
    const countdown = useCountdown(props.alert.seconds);

    return (        
        <div className={`alert ${props.alert.color}`}>
            <div className='alert-title'>
                <h1 onClick={() => props.removeAlert(props.alert.id)}>
                    <i className={`${props.alert.icon}`} /> <span className='hide-sm'>{props.alert.title}</span>
                </h1>
                <span className='alert-countdown'>{countdown}</span>
                <button className='btn btn-link btn-sm alert-btn hide-sm' onClick={() => props.removeAlert(props.alert.id)}>
                    <i className='far fa-times-square fa-3x' />
                </button>
            </div>
            <div className='alert-items'>
                <ul>
                    {props.alert.msg.map((item) => (
                        <li key={item}>
                            <i className='fas fa-chevron-circle-right'></i> <span>{item}</span>
                        </li>
                    ))}
                </ul>
            </div>
        </div>        
    );
}

另外,在使用中更改依赖于先前值的状态时,setMinutes(minutes - 1)使用更新程序功能,而不是简单地提供一个值。在这种情况下,请确保您的更新将是准确的,并且不会对过时的数据进行更新,从而最终导致错误状态。您可以在此处了解更多信息:useState参考

因此,您的钩子应如下所示:

const useCountdown = (m = 1, s = 10) => {
  const [minutes, setMinutes] = useState(m)
  const [seconds, setSeconds] = useState(s)

  useEffect(() => {
    let myInterval = setInterval(() => {
      if (seconds > 0) {
        setSeconds((seconds) => seconds - 1)
      }
      if (seconds === 0) {
        if (minutes === 0) {
          clearInterval(myInterval)
        } else {
          setMinutes((minutes) => minutes - 1)
          setSeconds(59)
        }
      }
    }, 1000)
    return () => {
      clearInterval(myInterval)
    }
  })

  const finalSeconds = seconds < 10 ? `0${seconds}` : `${seconds}`

  return `${minutes}:${finalSeconds}`
}

本文收集自互联网,转载请注明来源。

如有侵权,请联系 [email protected] 删除。

编辑于
0

我来说两句

0 条评论
登录 后参与评论

相关文章