Search code examples
reactjssettimeoutsetintervaluse-stateclearinterval

Use effect doesn't see state


i'm working on a continuous notification (every 3 seconds and max 1 min), until user accept pending orders.

This is my code. usePrevious is custom hook that gives me previous value of state.

I don't mind why, when setTimeout executes clearInterval(), 'notification' is null. This problem cause loop in sound.

[.....]
    const [notification, setNotification] = useState(null)

    const previousOrderCount = usePrevious(newOrderCount)
    const previousPendingReservationsLength = usePrevious(
        pendingReservationsLength
    )

    const isToneActive = useRef(false)

    // sound notify for new orders
    useEffect(() => {
        if (
            !isToneActive.current &&
            newOrderCount > previousOrderCount &&
            user?.profile?.role === "manager"
        ) {

            // this prevent multiple triggers of sound
            isToneActive.current = true

            setNotification(setInterval(() => playNotification(), 3000))
            setTimeout(() => {
                clearInterval(notification)
                isToneActive.current = false
            }, 60000)
        }
    }, [
        newOrderCount,
        playNotification,
        user,
        previousOrderCount,
        stopNotify,
        notification,
        setNotification,
    ])

[....]


Solution

  • I would use another React ref to hold a reference to the interval timer. The issue here is that React state updates are asynchronous, so the setNotification state update doesn't immediately update the notification state for the setTimeout callback that encloses the current null state value.

    const notificationRef = React.useRef(null);
    
    ...
    
    notificationRef.current = setInterval(() => playNotification(), 3000);
    setTimeout(() => {
      clearInterval(notificationRef.current);
      notificationRef.current = null;
      isToneActive.current = false;
    }, 60000);