javascript css 137 lines · 4 tabs

Build a React Toast Notification System with Context Provider, Hook, and Portal

Shared by codesnips Aug 2026
4 tabs
import React, { createContext, useCallback, useMemo, useRef, useState } from 'react';
import { ToastViewport } from './ToastViewport';

export const ToastContext = createContext(undefined);

let counter = 0;
function nextId() {
  counter += 1;
  return `toast-${counter}-${Date.now()}`;
}

export function ToastProvider({ children, defaultDuration = 4000 }) {
  const [toasts, setToasts] = useState([]);
  const timers = useRef(new Map());

  const removeToast = useCallback((id) => {
    const timer = timers.current.get(id);
    if (timer) {
      clearTimeout(timer);
      timers.current.delete(id);
    }
    setToasts((prev) => prev.filter((t) => t.id !== id));
  }, []);

  const addToast = useCallback(
    ({ message, variant = 'info', duration = defaultDuration }) => {
      const id = nextId();
      setToasts((prev) => [...prev, { id, message, variant }]);

      if (duration > 0) {
        const timer = setTimeout(() => removeToast(id), duration);
        timers.current.set(id, timer);
      }
      return id;
    },
    [defaultDuration, removeToast]
  );

  const value = useMemo(
    () => ({
      addToast,
      removeToast,
      success: (message, opts) => addToast({ ...opts, message, variant: 'success' }),
      error: (message, opts) => addToast({ ...opts, message, variant: 'error' }),
      info: (message, opts) => addToast({ ...opts, message, variant: 'info' }),
    }),
    [addToast, removeToast]
  );

  return (
    <ToastContext.Provider value={value}>
      {children}
      <ToastViewport toasts={toasts} onDismiss={removeToast} />
    </ToastContext.Provider>
  );
}
4 files · javascript, css Explain with highlit

This snippet shows a complete, dependency-free toast notification system built on React's own primitives: a context provider that owns the toast queue, a custom hook that exposes an ergonomic API, and a portal renderer that paints toasts above the rest of the app. The split mirrors real responsibilities — state lives in one place, consumers get a tiny surface, and rendering escapes the normal DOM tree.

In ToastProvider, the toast list is held in useState and mutated through useCallback-memoized functions so the context value stays stable across renders. The core primitive is addToast, which generates a unique id, appends a toast, and — unless duration is 0 — schedules an auto-dismiss via setTimeout. Timers are tracked in a useRef map so removeToast can clearTimeout when a user dismisses early, preventing a stale callback from firing against an already-removed id. Convenience wrappers (success, error, info) are derived from addToast and bundled into a useMemo-stabilized value, which matters because an unstable context object would re-render every consumer on each state change. The provider also mounts the ToastViewport so applications only wrap their tree once.

useToast is a thin consumer hook that reads the context with useContext and throws when it is undefined. That guard turns a silent null-reference bug into a clear, actionable error whenever the hook is called outside the provider — a small but important developer-experience safeguard for any context-based library.

ToastViewport is where the portal pattern earns its place. Rendering through createPortal into a dedicated document.body node sidesteps overflow: hidden, transform, and z-index stacking contexts from ancestor elements that would otherwise clip or bury the toasts. The container carries role="region" and aria-live="polite" so screen readers announce new messages without stealing focus, and each toast is a button so it is keyboard-dismissible. The host node is created lazily in a useState initializer and appended in an effect, with cleanup on unmount.

The trade-offs are worth noting: this design keeps everything in memory, so toasts do not survive navigation in non-SPA setups, and the timer-in-ref approach must clear on unmount to avoid leaks. For most apps this pattern is the sweet spot — no external state library, full styling control, and an API as simple as toast.success('Saved').


Related snips

html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Semantic HTML Example</title>

Semantic HTML5 elements and accessibility best practices

html html5 semantics
by Alex Chang 2 tabs
javascript
import { Controller } from "@hotwired/stimulus"

export default class extends Controller {
  static targets = ["form"]
  static values = { delay: { type: Number, default: 250 } }

Debounced live search with Stimulus + Turbo Streams

rails hotwire stimulus
by codesnips 4 tabs
typescript
import axios, { AxiosError } from 'axios'
import { v4 as uuidv4 } from 'uuid'

const api = axios.create({
  baseURL: import.meta.env.VITE_API_URL || 'http://localhost:3000/api/v1',
  timeout: 15000,

Axios API client with interceptors

react axios api
by Maya Patel 1 tab
typescript
import { create } from 'zustand'
import { persist } from 'zustand/middleware'

interface FilterState {
  search: string
  category: string | null

Zustand for lightweight state management

react zustand state-management
by Maya Patel 2 tabs
swift
import SwiftUI

struct ContentView: View {
    @State private var username = ""
    @State private var isLoggedIn = false
    @StateObject private var viewModel = LoginViewModel()

SwiftUI declarative UI with state management

swift swiftui ios
by Sofia Martinez 2 tabs
html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Form Validation Example</title>
  <style>

HTML forms with validation and accessibility

html forms validation
by Alex Chang 1 tab

Share this code

Here's the card — post it anywhere.

Build a React Toast Notification System with Context Provider, Hook, and Portal — share card
Link copied