# Toast Notifications

> Build a reusable toast notification system that surfaces messages from any component through a Context provider, renders them in a portal, and auto-dismisses them with timers.

- Difficulty: intermediate
- Tags: useContext, createPortal, timers
- Estimated time: 90 minutes
- Tests: 10
- Requires subscription: yes

## Overview

Build a reusable toast notification system that surfaces messages from any component through a Context provider, renders them in a portal, and auto-dismisses them with timers.

## Requirements

- On load, the app shows the toast viewport (`data-testid="toast-viewport"`), the three quick trigger buttons (`data-testid="toast-success"`, `data-testid="toast-error"`, `data-testid="toast-info"`), and the "clear all" button (`data-testid="dismiss-all"`), with no toasts visible.
- Clicking a quick trigger shows a toast with that trigger's own message and type.
- Each toast is rendered with a unique id (`data-testid="toast-<id>"`) and a type marker (`data-type="success|error|info"`).
- Clicking triggers one after another stacks multiple toasts without overwriting earlier ones.
- Each toast dismisses itself automatically after a short default duration (~4 seconds).
- Each toast exposes a dismiss button (`data-testid="dismiss-<id>"`) that removes only that toast.
- The "clear all toasts" button removes every visible toast at once.
- The user can type a message into the custom field (`data-testid="message-input"`) and send it with the notify button (`data-testid="notify-button"`); a toast with exactly that message appears and the field is cleared.
- Sending an empty or whitespace-only message shows nothing.
- Hovering a toast pauses its auto-dismiss; leaving the toast resumes it and it dismisses shortly after.
- Toast identifiers stay unique across the app's lifetime: after clearing all toasts, the next toast gets a fresh id instead of reusing an old one.

## Notes

- Everything read-only lives in `common/`: `Button.tsx`, `ToastItem.tsx`, `ToastViewport.tsx`, `types.ts`. The viewport already portals a live region (`aria-live="polite"`); build the rest in `App.tsx` and `ToastContext.tsx`.
- The starter `App.tsx` is wired to nothing on purpose: import the toast API, wrap the demo in the provider, and connect the quick buttons, "clear all" button, and custom-message field.
- Code that calls `useToast()` must render inside the provider — give the demo an inner component below it. Until you do, `useToast()` throws (that's your signpost).
- The context's default is `null`, not empty functions: if `useToast()` is called outside the provider it throws a clear error, so a wiring mistake is loud instead of silently doing nothing.
- Make the message input a controlled component with `useState`: "notify" sends its value and then clears it.
- Give each toast an id from an ever-increasing `useRef` counter, so ids are never reused after "clear all".
- `notify` returns early when `message.trim()` is empty — a whitespace-only submit creates no toast.
- Each toast owns one auto-dismiss `setTimeout`; keep its handle in a `useRef<Map<id, handle>>`, `clearTimeout` it on dismiss/pause, and clear all handles on unmount.
- For pause/resume, store `startedAt` + `remaining` per toast: pause computes the elapsed time, resume re-arms a timer for `remaining`; if `remaining` already hit 0, dismiss instead of re-arming.
- APIs: [`createContext`](https://react.dev/reference/react/createContext), [`useContext`](https://react.dev/reference/react/useContext), [`createPortal`](https://react.dev/reference/react-dom/createPortal), [`useEffect`](https://react.dev/reference/react/useEffect), [`useState`](https://react.dev/reference/react/useState), [`useRef`](https://react.dev/reference/react/useRef), [`setTimeout`](https://developer.mozilla.org/en-US/docs/Web/API/setTimeout), [`aria-live`](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-live).


## Tests

01. renders the toast viewport and the three trigger buttons
02. shows a toast when a quick toast button is clicked
03. stacks multiple toasts without overwriting
04. auto-dismisses a toast after the default duration
05. removes a single toast with its dismiss button
06. clears all toasts with the clear all button
07. ignores a notify when the message is empty or whitespace-only
08. sends a custom message with the notify button
09. pauses auto-dismiss while hovered and resumes after leaving
10. keeps unique ids after clearing all toasts


