typescript 116 lines · 4 tabs

Imperative Toast Notification System with React Context and Auto-Dismiss

Shared by codesnips Aug 2026
4 tabs
import { createContext } from 'react';

export type ToastVariant = 'success' | 'error' | 'info';

export interface Toast {
  id: number;
  variant: ToastVariant;
  message: string;
  duration?: number;
}

export interface ToastContextValue {
  toasts: Toast[];
  add: (toast: Omit<Toast, 'id'>) => number;
  remove: (id: number) => void;
}

export const ToastContext = createContext<ToastContextValue | null>(null);
4 files · typescript Explain with highlit

This snippet shows a small toast notification system built around React's Context API, exposing both a declarative provider and an imperative toast()-style API that can be called from anywhere in the tree. The central idea is to keep toast state in one place (a provider) while letting consumers fire notifications without prop drilling or manually threading callbacks.

In ToastContext.ts, the shape of the context is defined with a ToastContextValue interface holding add and remove functions plus the current toasts array. The context defaults to null so that useToast can throw a clear error when a component tries to use toasts outside the provider — a common guard that turns a silent no-op into an actionable mistake. The Toast type carries an id, a variant, the message, and an optional duration.

ToastProvider.tsx owns the list in useState and generates stable ids with a useRef counter, avoiding Date.now() collisions when several toasts fire in the same tick. add is wrapped in useCallback and, when a duration is present, schedules removal with setTimeout; remove filters the toast out by id. Because add and remove are stable, the memoized value only changes when toasts changes, keeping consumers from re-rendering needlessly. The provider renders its children alongside a ToastViewport that portals into document.body, so toasts float above the app regardless of overflow or stacking-context issues in nested components.

useToast.ts reads the context and returns a friendly imperative surface: success, error, and info helpers built on top of add, each defaulting the variant and a sensible duration. This is where the imperative API lives — a caller writes toast.success('Saved') rather than mutating state directly.

The pattern's trade-off is that toasts live in React state, so firing them from outside React (e.g. a plain module) requires bridging through a component. The portal-based viewport and stable-callback memoization are the details that make it reliable at scale.


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
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
typescript
import React from "react";

type FallbackProps = {
  error: Error;
  reset: () => void;
};

React Error Boundary + error reporting hook

react frontend error-boundary
by codesnips 3 tabs
erb
<h1>Products</h1>

<%= form_with url: products_path, method: :get,
              data: { turbo_frame: "products_list", turbo_action: "advance" } do |f| %>
  <div class="filters">
    <%= f.text_field :q, value: params[:q], placeholder: "Search products" %>

Frame navigation that targets a specific frame via form_with

rails hotwire turbo
by codesnips 3 tabs

Share this code

Here's the card — post it anywhere.

Imperative Toast Notification System with React Context and Auto-Dismiss — share card
Link copied