Home > Back-end >  Bug with SetInterval: Timer works initially, but on each re-render, the timer speeds up and displays
Bug with SetInterval: Timer works initially, but on each re-render, the timer speeds up and displays

Time:02-05

I am trying to build a modal which, when opened, only displays for ten seconds and then stops showing after that time.

I have tried to code this mechanism using setInterval and useState.

At the moment, this only works the first time when I click "Open Modal".

Each subsequent time I click "Open Modal", the timer messes up and counts down from less and less time and only displays for 5...then 2.. then 1 second.

After googling, everyone has suggested adding "ClearInterval" to the useEffect hook but that has not worked for me.

import gsap from 'gsap'
import Image from 'next/future/image'
import { type FC, useEffect, useState } from 'react'
import { useRef } from 'react'
import { Transition } from 'react-transition-group'

import logo from '@/assets/brand/logo.svg'

import styles from './Modal.module.scss'

const Modal: FC<any> = ({ show, setShow }) => {
  const container = useRef<HTMLDivElement>(null)
  const q = gsap.utils.selector(container)
  const [timeLeft, setTimeLeft] = useState(10)

  useEffect(() => {
    const intervalId = setInterval(() => {
      setTimeLeft((prev) => prev - 1)
    }, 1000)
    if (!show) return
    () => {
      clearInterval(intervalId)
    }
  }, [show])

  useEffect(() => {
    if (timeLeft > 0) return
    setShow(false)
    setTimeLeft(10)
  }, [setShow, timeLeft])

  const onEnter = () => {
    gsap.set(container.current, { opacity: 0 })
    gsap.set(q('*'), { opacity: 0, y: 8 })
  }

  const onEntering = () => {
    gsap.timeline().to(container.current, { opacity: 1, duration: 0.3 }).to(q('*'), { opacity: 1, y: 0, stagger: 0.08 })
  }

  const onExiting = () => {
    gsap.to(container.current, { autoAlpha: 0, duration: 0.3 })
  }

  return (
    <Transition
      in={show}
      timeout={600}
      mountOnEnter={true}
      unmountOnExit={true}
      nodeRef={container}
      onEnter={onEnter}
      onEntering={onEntering}
      onExiting={onExiting}>
      <div ref={container} className={styles.container}>
        <Image src={logo} alt="Loopspeed" width={214} height={44} style={{ objectFit: 'contain' }} />
 <p>Blah Blah </p>
          This modal will close in <strong>{timeLeft}</strong> seconds.
        </p>
      </div>
    </Transition>
  )
}

export default Modal

Please let me know if I need to post any more code.

I have tried using setInterval and useState, and also setInterval, clearInterval and useState.

CodePudding user response:

Just a misc error with useEffect - if you setInterval in the useEffect - ensure you are clearing it, for useEffect hook the "cleanup" function needs to be returned - return () => ....

useEffect(() => {
  if (!show) return;

  const intervalId = setInterval(() => {
    setTimeLeft((prev) => prev - 1)
  }, 1000)
  
  return () => {
    clearInterval(intervalId)
  }
}, [show]);
  • Related