# 6890 MINToast Notifications
# 68
90 MIN
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.
Solution preview
Here's the result you'll build by completing this challenge.
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 inApp.tsxandToastContext.tsx. - The starter
App.tsxis 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: ifuseToast()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
useRefcounter, so ids are never reused after "clear all". notifyreturns early whenmessage.trim()is empty — a whitespace-only submit creates no toast.- Each toast owns one auto-dismiss
setTimeout; keep its handle in auseRef<Map<id, handle>>,clearTimeoutit on dismiss/pause, and clear all handles on unmount. - For pause/resume, store
startedAt+remainingper toast: pause computes the elapsed time, resume re-arms a timer forremaining; ifremainingalready hit 0, dismiss instead of re-arming. - APIs:
createContext,useContext,createPortal,useEffect,useState,useRef,setTimeout,aria-live.
Tests
- renders the toast viewport and the three trigger buttons
- shows a toast when a quick toast button is clicked
- stacks multiple toasts without overwriting
- auto-dismisses a toast after the default duration
- removes a single toast with its dismiss button
- clears all toasts with the clear all button
- ignores a notify when the message is empty or whitespace-only
- sends a custom message with the notify button
- pauses auto-dismiss while hovered and resumes after leaving
- keeps unique ids after clearing all toasts