medium+200 XP
Build a toast notification system
A button triggers a toast notification. Triggering it again while toasts are already showing stacks another one alongside them, rather than replacing it.
Each toast disappears on its own after 2 seconds, or immediately if its own close button is clicked.
Requirements
- A button `data-testid="show-toast"` adds one toast each time it is clicked.
- Each toast renders as `data-testid="toast"` inside a `data-testid="toast-container"`.
- Multiple toasts triggered in quick succession all stack simultaneously — the count of visible toasts matches the number of clicks.
- Each toast has its own `data-testid="toast-close"` button that removes only that toast.
- A toast that is not manually closed removes itself automatically after 2000ms.
Your workspace
import { useState } from "react"; let nextId = 1; export default function App() { const [toasts, setToasts] = useState([]); function showToast() { const id = nextId++; setToasts((current) => [...current, { id, message: "Action completed" }]); // TODO: this toast should remove itself automatically after 2000ms. } function closeToast(id) { // TODO: remove just this toast from state. } return ( <div style={{ fontFamily: "sans-serif", padding: 16 }}> <button data-testid="show-toast" onClick={showToast}> Show toast </button> <div data-testid="toast-container" style={{ marginTop: 16, display: "flex", flexDirection: "column", gap: 8 }} > {toasts.map((toast) => ( <div key={toast.id} data-testid="toast" style={{ padding: 12, background: "#333", color: "white", borderRadius: 6 }} > {toast.message} <button data-testid="toast-close" onClick={() => closeToast(toast.id)} style={{ marginLeft: 12 }} > × </button> </div> ))} </div> </div> ); }
Ready to check it?
5 tests run against your rendered component, right here in your browser. Sign in to claim the XP when you pass.
Stuck? The react course covers the ideas this problem is built on.