osmancakirio
osmancakirio

Reputation: 519

setTimeout runs indefinetly inside useEffect

I am trying to make a notification toast component. And I want it to be removed after 2 seconds (not-shown on the screed) Although, it is removed (not-shown on the screen via top:-100 argument), the component is getting rendered infinitely. You can see it from the console.log's I have placed inside the component and inside the useEffect call with setTimeout.

My expectation is that setTimeout should run setShowState after 2 seconds and then useEffect should do the cleanup and remove the timer. So everything is back to normal until showState changes.

import React, {useEffect, useState} from 'react'
import I18n from '../../i18n'
import styled from 'styled-components'
import {createGlobalStyle} from 'styled-components'
import {useSelector} from 'react-redux'

const NotificationStyle = createGlobalStyle`
    @media (max-width: 500px) {
        .notification_mssg {
            left: 10px;
        }
    }
`

const Container = styled.div`
  color: white;
  position: fixed;
  top: ${(props) => props.top}px;
  right: 16px;
  z-index: 2000;
  transition: top 0.5s ease;
`

const NoticitactionIcon = styled.div`
  float: left;
  font-size: 27px;
  width: 40px;
  height: 40px;
  text-align: center;
`

const NotificationMessage = styled.span`
  padding: 10px;
  line-height: 40px;
`

function NotificationAlertRoot(props) {

  const create_notification = useSelector((state) => state.notifications.create_notification)

  const {message, code} = create_notification.success_info
  const [showState, setShowState] = useState({top: -100, msg: message, bgColor: '#444'})

  // show notification
  useEffect(() => {
    setShowState({top: 96, msg: I18n.t(message), bgColor: backgroundColor(code)})
  }, [message, code])

  console.log('amIrendered', showState) // although showState doesn't change, component is getting rendered infinitely :/

  // hide notification after 2 seconds
  useEffect(() => {
    const timerId = setTimeout(() => {
      setShowState({
        top: -100,
        msg: '',
        bgColor: `#ffffff00`,
      })
      console.log("timerId", timerId) // I see timerId is changing so the problem most probably in this useEffect call.
    }, 2000)

    return () => {
      clearTimeout(timerId)
    }
  }, [showState])

  const notificationIcon = (bgColor) => {
    switch (bgColor) {
      case '#32c786':
        return (
          <NoticitactionIcon style={{background: '#2aa872'}}>
            <i className="zmdi zmdi-info" />
          </NoticitactionIcon>
        )
      case '#ffc721':
        return (
          <NoticitactionIcon style={{background: '#fabb00'}}>
            <i className="zmdi zmdi-alert-triangle" />
          </NoticitactionIcon>
        )
      case '#ff6b68':
        return (
          <NoticitactionIcon style={{background: '#ff4642'}}>
            <i className="zmdi zmdi-alert-circle" />
          </NoticitactionIcon>
        )
      default:
        return <span></span>
    }
  }

  function backgroundColor(code) {
    switch (Math.floor(code / 100)) {
      case 2:
        return '#32c786'
      case 3:
        return '#ffc721'
      case 4:
        return '#ff6b68'
      case 5:
        return '#ff6b68'
      default:
        return '#444'
    }
  }
  return (
    <React.Fragment>
      <NotificationStyle />
      <Container
        className="notification_mssg"
        top={showState.top}
        style={{background: showState.bgColor}}
      >
        {notificationIcon(showState.bgColor)}
        <NotificationMessage>{showState.msg}</NotificationMessage>
      </Container>
    </React.Fragment>
  )
}

export default NotificationAlertRoot


Do you have an idea what is wrong above?

Upvotes: 0

Views: 86

Answers (2)

ali varaste pour
ali varaste pour

Reputation: 335

I guess the problem comes from your dependency array. Your useEffect is dependent on showState and each time, you are calling setShowState in your useEffect when setShowState is called showState changes and then again, your useEffect gets invoked(it is dependent on ShowState), and again setShowState is called and ... infinity loop!

Upvotes: 1

osmancakirio
osmancakirio

Reputation: 519

I found the root of the problem. Sometimes you are too focused on something and you forget the little details of useEffect. It is always dangerous to provide objects as dependency arrays to useEffect. The dependency array values should be simple values. So now I introduced a new state (flag, setFlag) with just boolean values and I make the second useEffect just to follow that simple value. Everything is working just fine now.


import React, {useEffect, useState} from 'react'
import I18n from '../../i18n'
import styled from 'styled-components'
import {createGlobalStyle} from 'styled-components'
import {useSelector} from 'react-redux'

const NotificationStyle = createGlobalStyle`
    @media (max-width: 500px) {
        .notification_mssg {
            left: 10px;
        }
    }
`

const Container = styled.div`
  color: white;
  position: fixed;
  top: ${(props) => props.top}px;
  right: 16px;
  z-index: 2000;
  transition: top 0.5s ease;
`

const NotificationIcon = styled.div`
  float: left;
  font-size: 27px;
  width: 40px;
  height: 40px;
  text-align: center;
`

const NotificationMessage = styled.span`
  padding: 10px;
  line-height: 40px;
`

function NotificationAlertRoot(props) {

  const create_notification = useSelector((state) => state.notifications.create_notification)

  const {message, code} = create_notification.success_info
  const [showState, setShowState] = useState({top: -100, msg: message, bgColor: '#444'})
  const [flag, setFlag] = useState(false) // when you follow the showState at the second useEffect you have an infinite loop. Because it is an object.

  // show notification
  useEffect(() => {
    setShowState({top: 96, msg: I18n.t(message), bgColor: backgroundColor(code)})
    setFlag(true)
  }, [message, code])

  // hide notification after 2 seconds
  useEffect(() => {
    const timerId = setTimeout(() => {
      setShowState({top: -100,msg: '', bgColor: `#ffffff00`})
      setFlag(false)
    }, 2000)

    return () => {
      clearTimeout(timerId)
    }
  }, [flag]) // showState

  const notificationIcon = (bgColor) => {
    switch (bgColor) {
      case '#32c786':
        return (
          <NotificationIcon style={{background: '#2aa872'}}>
            <i className="zmdi zmdi-info" />
          </NotificationIcon>
        )
      case '#ffc721':
        return (
          <NotificationIcon style={{background: '#fabb00'}}>
            <i className="zmdi zmdi-alert-triangle" />
          </NotificationIcon>
        )
      case '#ff6b68':
        return (
          <NotificationIcon style={{background: '#ff4642'}}>
            <i className="zmdi zmdi-alert-circle" />
          </NotificationIcon>
        )
      default:
        return <span></span>
    }
  }

  const backgroundColor = (code) => {
    switch (Math.floor(code / 100)) {
      case 2:
        return '#32c786'
      case 3:
        return '#ffc721'
      case 4:
        return '#ff6b68'
      case 5:
        return '#ff6b68'
      default:
        return '#444'
    }
  }
  return (
    <React.Fragment>
      <NotificationStyle />
      <Container
        className="notification_mssg"
        top={showState.top}
        style={{background: showState.bgColor}}
      >
        {notificationIcon(showState.bgColor)}
        <NotificationMessage>{showState.msg}</NotificationMessage>
      </Container>
    </React.Fragment>
  )
}

export default NotificationAlertRoot





Upvotes: 0

Related Questions