UX Utilities and API Client
Building Better UX: Toast Notifications and API Clients
Welcome back! You’ve already built a working dashboard and tasks list that fetch and display data from your backend. You’ve structured your app with layouts and reusable components.
Now we’ll enhance your app’s user experience (UX) and developer experience (DX) by adding two essential utilities:
- Toast notifications – small, non-intrusive messages that inform users when an action succeeds, fails, or needs attention.
- Reusable API client – a central helper that standardizes how your app communicates with the backend.
Both of these are critical in professional web apps: toasts make your app feel interactive and friendly, while a shared API client keeps your code consistent, clean, and error-free.
To make the lesson easier to follow, we’ll treat this unit as two mini-sections:
- Toast Notifications with React Context — global user feedback from anywhere in the dashboard.
- Reusable API Client and Response Envelopes — consistent request helpers for the course backend.
Quick Recap: Where We Are
Quick Recap: Where We Are
Before we jump into new concepts, let’s step back and see what you’ve built so far:
| Feature | What It Does | Key Files |
|---|---|---|
| Layouts | Provide consistent structure (header, sidebar, navigation). | src/app/layout.tsx, src/app/(dashboard)/layout.tsx |
| Dashboard Stats | Fetches data and shows total, completed, and incomplete tasks. | src/app/(dashboard)/page.tsx |
| Tasks List | Displays each task with title, status, and due date. | src/app/(dashboard)/tasks/page.tsx |
| Reusable Components | Shared UI components like Button and TaskRow. | src/components/ui/Button.tsx, src/components/tasks/TaskRow.tsx |
So far, the user can view data, but they don’t get feedback when they perform actions (like creating or failing to create a task).
We’ll fix that now with toasts and a centralized API helper.
Toast Notifications: Giving Users Feedback
A toast is a temporary notification that appears on the screen — usually in a corner — to inform the user of an event.
They’re lightweight, don’t interrupt the user’s workflow, and automatically disappear after a few seconds.
Typical examples:
✅ “Task saved successfully!”
❌ “Failed to connect to the server.”
ℹ️ “Changes will be autosaved.”
The Toast Implementation
Let’s look at how the toast system is built in your project:
Step 1: What is React Context and Why Use It?
React Context is a way to share data between components without passing props manually down multiple layers.
In your case:
- You want any component (like buttons, forms, or modals) to show a toast.
- Instead of passing a
showToast()function everywhere, you use Context. - The Context Provider stores and provides this functionality globally.
- The
useToast()hook lets any component access it easily.
So, Ctx here holds the functions success, error, and info that all components can use — as long as they are wrapped by the ToasterProvider.
Think of Context like a global state manager, but scoped to what you need (in this case, toast behavior).
Step 2: The Toaster Provider
ToasterProvider wraps your app and manages all the active toast messages.
- The
toastsstate holds an array of{ id, message, type }objects — each representing a toast. - Every time you add a toast, you push a new object into that array; when it expires, you remove it.
Step 3: The push Function and useCallback
Here’s what’s happening:
push()adds a new toast with a unique ID and automatically removes it after 2.5 seconds.setToasts()uses the state updater function form (setToasts((t) => [...t, ...])) to ensure it works with the latest state.
Now, let’s understand why we wrap it with useCallback.
useCallback: What It Does and Why It’s Important
useCallback(fn, deps) returns a memoized version of a function that only changes if the dependencies change.
For this course, you only need to know that useCallback keeps the push function reference stable so the provider value does not change unnecessarily.
Why this matters: stable functions help Context consumers avoid extra re-renders. You’ll see deeper memoization patterns later.
Step 4: Creating the Context Value with useMemo
This defines the actual API that child components will use.
Each function (success, error, info) calls push() with a different toast type.
We use useMemo to cache the value object so it doesn’t get recreated on every render.
useMemo: What It Does and Why It’s Important
useMemo(factory, deps) returns a memoized value (the result of running factory) that only changes when dependencies do.
Without it, this value object would be rebuilt every render, causing re-renders in all components using the context.
💡 In short:
useMemo= memoizes values or objects.- Prevents unnecessary re-renders by keeping stable references.
useCallbackis the same idea but for functions.
Difference Summary:
| Hook | Memoizes | Typical Use |
|---|---|---|
useCallback(fn, deps) | A function | When passing event handlers to children or storing in context |
useMemo(factory, deps) | A computed value/object | When expensive calculations or objects shouldn’t be recreated every render |
By combining useCallback and useMemo, you ensure that the toast system stays efficient, with no wasted re-renders across your entire app.
Step 5: Rendering the Toasts
Ctx.Providermakes the value available to all child components.- The
<div>renders all current toasts in a stack (space-y-2) in the top-right corner of the screen. - Each toast’s color depends on its type: green, red, or gray.
- Because of the timer inside
push(), toasts remove themselves automatically after 2.5 seconds.
Step 6: The useToast Hook
This is a custom hook that:
- Retrieves the toast context with
useContext(Ctx). - Throws a clear error if the provider isn’t wrapping the component tree (a helpful safeguard).
Now, any component can call: const toast = useToast(); and then use toast.success(), toast.error(), or toast.info() anywhere in the app.
Example: Triggering Toasts
You can trigger these messages in real API call handlers — for example, after creating, updating, or deleting a task.
The Reusable API Client
Now that you can show toasts for feedback, let’s look at how your app communicates with the backend.
Without an API client, you’d have to write repetitive code like this everywhere:
That’s fine once or twice, but across many files it becomes messy.
Instead, we create a single reusable client that wraps all this logic.
The Full API Client
How It Works
- Wraps
fetchwith consistent JSON parsing. - Interprets the course backend’s standardized response envelope — either
{ data }or{ error }. - This simplified client assumes failed requests return the course backend’s
{ error }envelope; it is not a complete general-purpose HTTP error handler. - Ensures every request in your app behaves the same way.
Standardized HTTP Methods
Each function:
- Defines the HTTP method (
GET,POST,PUT,PATCH,DELETE). - Adds standard headers (like
'Content-Type': 'application/json'). - Uses
request()to parse and handle errors automatically.
This saves huge amounts of boilerplate, especially when used throughout your app.
Example: Combining the API Client with Toasts
- The API client sends the request.
- If there’s an error,
toast.error()gives the user immediate feedback. - If successful,
toast.success()confirms it worked.
Summary & What’s Next
In this lesson, you learned:
- How React Context provides shared functionality (like toast management) to your entire app.
- How
useCallbackanduseMemoimprove performance by preventing unnecessary re-renders. - The difference between
useCallback(for functions) anduseMemo(for computed values). - How to design a ToasterProvider that manages, displays, and auto-cleans up toast messages.
- How to build a reusable API client that handles requests consistently.
- How to combine both tools to give users instant feedback when actions succeed or fail.
Next, you’ll apply these utilities throughout your project to make your task management app feel polished and responsive — just like a production-grade web application.
