Search code examples
javascriptreactjsreduxsetintervalreact-toastify

How to work with setInterval to trigger an event? JavaScript/React-toastify


I have a redux store that contains an object that looks like

rooms: {
    room01 : { timestamp: 10}
    room02 : { timestamp: 10}
}

When a user clicks on a button the timestamp is set to 10 (via dispatch). If there is a timestamp I count down to 0 and then set a Notification using react-toast across the app. The problem is I don't have roomId available on Notification component because Notification component has to be placed at app root otherwise it's get unmounted and my Notification() logic doesn't work. (I could access the rooms in Notification or App but it comes in an array for eg ['room01', 'room02'] which is also in redux but how do I select both rooms and access their timestamp as well as run the function to count down?).

Basically I need to check if there is timestamp in redux store for each room and if there is, count down to 0 and display notification with the roomId. The notification/setinterval should work while navigating to different pages.

My app component 

import React from 'react';
import { Route, Switch, BrowserRouter } from 'react-router-dom';

import Home from './Home';
import 'react-toastify/dist/ReactToastify.css';
import Notification from '../features/seclusion/Notification';

const App = () => {
    return (
        <div>
            <Notification /> // if I pass in room id like so roomId={'room02'} I cant get it to work for one room
            <BrowserRouter>
                <Switch>
                    <Route exact path="/room/:id" component={Room} />
                    <Route exact path="/" component={Home} />
                </Switch>
            </BrowserRouter>
        </div>);
};

export default App;

import React, { useState, useEffect } from 'react';
import { useSelector } from 'react-redux';
import 'react-toastify/dist/ReactToastify.css';
import { ToastContainer, toast, Slide } from 'react-toastify';
import moment from 'moment';

export default function Notification() {
    
    const timestamp =  useSelector(state =>  state.rooms); // need to get timestamp  for room01 and room02 
    
    const [timestamp, setTimestamp] = useState(null);
    const [timerId, setTimerId] = useState(null);

    const notify = () => {
        toast(<div>alert</div>,
            {
                position: "top-center",
            });
    }; 

    useEffect(() => {
        if (timestamp) {
            const id = setInterval(() => {
               setTimestamp((timestamp) =>  timestamp - 1)
            }, 1000)

            setTimerId(id);

        }
        return () => {
            clearInterval(timerId);
        };
    }, [timestamp]);

    useEffect(() => {
        if(timestamp === 0){
            notify();
            clearInterval(timerId); 
        }
    }, [timestamp])

    return (
        <ToastContainer
            newestOnTop={true}
            transition={Slide}
            autoClose={false}
        />
    );

}

Solution

  • You could do the following among other approaches, but i think the following is the best because it makes the Notification component more reusable, and also it makes better use of the separation of responsibilities, makes your code easier to read, and most importantly is aligned with the declarative mindset of React.

    const App = () => {
        const rooms = state.rooms // this is not redux syntax but i will leave that to you
        return (
            <div>
                {Object.entries(rooms).map(([roomId, roomDetails]) => {
                  const {timestamp} =  roomDetails;
                  // Obviously you now need to modify your Notification component to handle these props
                  return <Notification 
                          timestamp={timestamp} 
                          roomId={roomId} // actually its better to create the Notification component without the roomId prop and use the roomId to induce the message prop, this makes Notification component more reusable across other components 
                          key={`${roomId}-${timestamp}`} 
                          message="You might send message here instead of doing that inside the Notification component" 
                        />
                        // You might be interested to rename Notification to DelayedNotification or something else 
                }}
                <BrowserRouter>
                    <Switch>
                        <Route exact path="/room/:id" component={Room} />
                        <Route exact path="/" component={Home} />
                    </Switch>
                </BrowserRouter>
            </div>);
    };