javascript
48 lines · 7 steps
Building a toast notification system in React
A Context provider manages a list of transient toasts and exposes notify and dismiss to any component in the tree.
Explained by
highlit
1import { createContext, useContext, useState, useCallback, useRef } from "react";
2
3const ToastContext = createContext(null);
4
5export function ToastProvider({ children, defaultDuration = 4000 }) {
6 const [toasts, setToasts] = useState([]);
7 const idRef = useRef(0);
8
9 const dismiss = useCallback((id) => {
10 setToasts((prev) => prev.filter((t) => t.id !== id));
11 }, []);
12
13 const notify = useCallback(
14 ({ message, variant = "info", duration = defaultDuration }) => {
15 const id = ++idRef.current;
16 setToasts((prev) => [...prev, { id, message, variant }]);
17 if (duration > 0) {
18 setTimeout(() => dismiss(id), duration);
19 }
20 return id;
21 },
22 [defaultDuration, dismiss]
23 );
24
25 return (
26 <ToastContext.Provider value={{ notify, dismiss }}>
27 {children}
28 <div className="toast-viewport" role="region" aria-live="polite">
29 {toasts.map((toast) => (
30 <div key={toast.id} className={`toast toast--${toast.variant}`}>
31 <span>{toast.message}</span>
32 <button onClick={() => dismiss(toast.id)} aria-label="Dismiss">
33 ×
34 </button>
35 </div>
36 ))}
37 </div>
38 </ToastContext.Provider>
39 );
40}
41
42export function useToast() {
43 const ctx = useContext(ToastContext);
44 if (!ctx) {
45 throw new Error("useToast must be used within a ToastProvider");
46 }
47 return ctx;
48}
01 / 01
STEP 01
‹ swipe to step through ›
Walkthrough
Space play
←→ step
click any line
Three takeaways
- 1Context lets you expose imperative actions like notify and dismiss to the whole tree without prop drilling.
- 2A ref-backed counter generates stable unique ids that survive re-renders without triggering them.
- 3Wrapping the context in a custom hook lets you enforce correct usage with a clear error.
Related explainers
javascript
function evaluate(expression) { const tokens = tokenize(expression); let pos = 0;
Building a recursive descent calculator
parsing
recursion
operator-precedence
Intermediate
8 steps
typescript
import { Injectable, effect, signal, computed } from '@angular/core'; interface Preferences { theme: 'light' | 'dark';
A signal-based preferences store in Angular
signals
state-management
persistence
Intermediate
7 steps
javascript
import { useState, useEffect, useCallback, useRef } from 'react'; const cache = new Map(); const inflight = new Map();
Building a stale-while-revalidate hook in React
caching
request-deduplication
custom-hooks
Advanced
10 steps
go
func (w *Watcher) resetDebounce(d time.Duration) { if !w.timer.Stop() { select { case <-w.timer.C:
Debouncing a stream of events in Go
debounce
timers
channels
Advanced
7 steps
typescript
import { useEffect, useState } from "react"; interface Section { id: string;
Building a scroll-spy hook in React
custom-hooks
intersectionobserver
dom-observation
Intermediate
8 steps
javascript
import { useEffect, useRef, useState } from 'react'; export function useDelayedFlag(active, delay = 300) { const [visible, setVisible] = useState(false);
Delaying a loading spinner with a React hook
custom-hooks
debouncing
cleanup
Intermediate
8 steps
Share this explainer
Here's the card — post it anywhere.
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code
Embed this explainer
Drop the interactive walkthrough into a blog or docs. Views never cost a credit.
<iframe src="https://highlit.co/explainers/building-a-toast-notification-system-in-react-explained-javascript-899c/embed?autoplay=1" width="100%" height="520" loading="lazy" style="border:0"></iframe>
Autoplay is on by default — add ?autoplay=0 to start paused.