Using React for practice, I'm trying to build a small notification system that disappears after a given period using timeouts.
A sample scenario;
This would be no problem and clears out the only available timeout. The problem appears when I'm adding more:
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 }
}
                I think with a minor tweak/refactor you can instead use an array of queued timeouts. Don't use the useEffect hook to manage the timeouts other than using a single mounting useEffect to return an unmounting cleanup function to clear out any remaining running timeouts when the component unmounts.
Use enqueue and dequeue functions to start a timeout and enqueue a stored payload and timer id, to be cleared by the dequeue function.
const [timerQueue, setTimerQueue] = useState([]);
useEffect(() => {
  return () => timerQueue.forEach(({ timerId }) => clearTimeout(timerId));
}, []);
const enqueue = (id) => {
  const timerId = setTimeout(dequeue, 3000, id);
  setTimerQueue((queue) =>
    queue.concat({
      id,
      timerId
    })
  );
};
const dequeue = (id) =>
  setTimerQueue((queue) => queue.filter((el) => el.id !== id));
Demo
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With