typescript 117 lines · 3 tabs

Frontend: toast notifications via a small event bus

Shared by codesnips Jan 2026
3 tabs
import React from 'react';
import { useToasts } from './useToasts';

export function Toaster() {
  const { toasts, dismiss } = useToasts();

  return (
    <div className="toast-stack" role="region" aria-label="Notifications">
      {toasts.map((t) => (
        <div
          key={t.id}
          className={`toast toast--${t.level}`}
          role={t.level === 'error' ? 'alert' : 'status'}
        >
          <span className="toast__message">{t.message}</span>
          <button
            type="button"
            className="toast__close"
            aria-label="Dismiss notification"
            onClick={() => dismiss(t.id)}
          >
            ×
          </button>
        </div>
      ))}
    </div>
  );
}
3 files · typescript Explain with highlit

This snippet shows how to build toast notifications that can be triggered from anywhere in an app — including outside the React tree, such as inside API interceptors or async utilities — without wiring the toast function through props or context. The core idea is a small typed publish/subscribe event bus that decouples the code that raises a toast from the code that renders it.

The toastBus file defines a minimal EventBus keyed by a ToastEvent payload. subscribe pushes a listener into a Set and returns an unsubscribe function, which is the idiomatic contract React's useEffect cleanup and useSyncExternalStore both expect. emit iterates a copied snapshot of the listeners so a handler that unsubscribes during dispatch cannot corrupt the iteration. The exported toast helper is the public API — callers just do toast.success('Saved') and never touch React. Using a Set gives O(1) add/remove and automatic de-duplication of identical listener references.

The useToasts hook bridges the bus into component state. It keeps a Toast[] array, subscribes once on mount, and appends incoming events with a generated id while pruning to the last few entries so a burst cannot grow the list unbounded. Each toast schedules its own removal via setTimeout, and the timers are tracked in a ref so the cleanup on unmount can clear them and avoid setting state after the component is gone — a common source of memory-leak warnings. dismiss is exposed for manual close buttons.

The Toaster component renders a fixed-position stack from that hook. It is mounted exactly once near the app root; everything else in the codebase simply imports toast. This is the key trade-off: a module-level singleton bus is intentionally global, which makes it trivially reachable but means tests should reset it between runs and there is only one active toast surface per bus.

The pattern is worth reaching for whenever an imperative signal must cross the boundary between non-React code and the view layer, and it generalizes cleanly to modals, error banners, or analytics events by swapping the payload type.


Related snips

typescript
export interface RetryOptions {
  retries: number;
  baseMs: number;
  maxMs: number;
  signal?: AbortSignal;
  onRetry?: (attempt: number, delay: number, err: unknown) => void;

Exponential backoff with jitter for retries

typescript reliability retry
by codesnips 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
javascript
import { Application } from "@hotwired/stimulus"
import FormSubmitController from "./controllers/form_submit_controller"

const application = Application.start()
application.debug = false

Disable submit button while Turbo form is submitting

rails hotwire stimulus
by codesnips 3 tabs

Share this code

Here's the card — post it anywhere.

Frontend: toast notifications via a small event bus — share card
Link copied