In my React app, I have a list of orders which are supposed to be shown to the user for only 30 seconds so each order has a value of 30 seconds for its duration propery:
[
{
...,
...,
duration: 30
},
{
...,
...,
duration: 30
},
...
]
I’m using Redux Toolkit to store their data so that I can render the UI of these items in various components. I tried to create an action which gets dispatched every 1 second to decrement the duration by one:
decrementCountdown: (state, action) => {
const order = state.entities[action.payload];
if (order) order.duration -= 1;
}
Then, in App.jsx
, I dispatch the action using setInterval
inside a loop:
useEffect(() => {
let countdown;
for (order of orders) {
// Run until the duration reaches zero
if (order.duration > 1) {
countdown = setInterval(() => dispatch(decrementCountdown(order?.id)), 1000);
}
}
return () => clearInterval(countdown);
}, [orders])
The challenging part is that the timers have to be synched so that everywhere that the items are shown, the same remaining time is shown and decremented.
The method I have used didn’t help me much. Especially when more that one order was present. In that case, the new order’s duration wouldn’t decrement and it caused an infinite loop inside the useEffect
.
Is there any way I can create a countdown for each one?
2
Answers
Thanks to @Alexey's response and comment I figured that I needed to create a separate Redux Slice dedicated to each order's duration. I was already using the
Entity Adapter
of Redux Toolkit so the order and duration slices looked like this:orderSlice.js
:orderDurationSlice.js
:After each order arrives via a
Socket
connection, it gets added into both of the slices (Only the orderid
gets added to theorderDuration
slice in order to correspond to the complete order object inside theorder
slice).The challenging part was to start the countdown timer for each one of the orders as soon as they arrive and also have it as a top-level service running independently of the component lifecycle so that each component that requires it could access it.
I was already using Redux Toolkit's
Listener Middleware
to handle theSocket
connection and after someone on Reddit recommended the use of Redux'smiddleware
to handle this, I realized that I just had to create anotherlistener
as a kind of countdown service.This listener would only run if the
orderDuration
slice is filled and the instance ofsetInterval
isundefined
.socketListenerMiddleware.js
:The main part which handles the countdown is here:
Here, only one instance of
setInterval
is created which loops over theorderDuration
array to get each one of the items and dispatches an action which decrements each order's duration by 1.If the order's duration gets to zero, two dispatched actions remove the order (in
order
slice) and its corresponding duration (inorderDuration
slice).Finally, outside of the loop an
if
condition checks if theorderDuration
slice gets empty and if so, it clears theinterval
and resets its instance.I then access each order's duration via selector's inside components.
Hope this solution helps anyone dealing with this kind of question.
Do you really need to keep countdown in store? It is very bad idea, because every second u will trigger rerenders of components that using this data.
Maybe u can create an CountDown component?
But if u need to keep countdowns in redux i recommend you to move it from orders objects and place it to array for example. In this array u will be able to keep counters and intervals for every order.