Using React for practice, I'm trying to build a small notification system that disappears after a given period using timeouts.
A sample scenario;
- User creates one notification and wait for it's to expire
- The cleanup function runs from useEffect and clears the timeout.
This would be no problem and clears out the only available timeout. The problem appears when I'm adding more:
- Render #1 - adding the first notification
- Render #2 - the cleanup function calls from render #1 for adding a new notification. This adds a new notification but clears a timeout before it's done.
- The timeout expires from render #2 so runs the cleanup function and clears the right timeout.
It's a fairly simple component, which renders a array of objects (with the timeout in it) from a Zustand store.
export const Notifications = () => { const { queue, } = useStore()
useEffect(() => {
if (!queue.length || !queue) return
// eslint-disable-next-line consistent-return
return () => {
const { timeout } = queue[0]
timeout && clearTimeout(timeout)
} }, [queue])
return (
<div className="absolute bottom-0 mb-8 space-y-3">
{queue.map(({ id, value }) => (
<NotificationComponent key={id} requestDiscard={() => id}>
{value}
</NotificationComponent>
))}
</div> ) }
My question is; is there any way to not delete a running timeout when adding a new notification? I also tried finding the last notification in the array by queue[queue.length - 1], but it somehow doesn't make any sense
My zustand store:
interface State {
queue: Notification[]
add: (notification: Notification) => void
rm: (id: string) => void
}
const useNotificationStore = c<State>(set => ({
add: (notification: Notification) =>
set(({ queue }) => ({ queue: [...queue, notification] })),
rm: (id: string) =>
set(({ queue }) => ({
queue: queue.filter(n => id !== n.id),
})),
queue: [],
}))
My hook for adding notifications;
export function useStoreForStackOverflow() {
const { add, rm } = useNotificationStore()
const notificate = (value: string) => {
const id = nanoid()
const timeout = setTimeout(() => rm(id), 2000)
return add({ id, value, timeout })
}
return { notificate }
}