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}
/>
);
}
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>);
};