Filtered Task View
Introduction: Why Add a Filtered View?
Welcome to your first lesson in the course Filters, State Sync & Production Polish.
So far, your app allows users to create, view, and edit tasks. But as the list of tasks grows, it becomes harder to focus on what matters most — for example, unfinished tasks or those already completed.
In this lesson, you’ll make your app smarter and easier to navigate by adding a Filtered Task View.
This new feature lets users:
- Click a Filters tab in the sidebar.
- Switch between Incomplete and Completed views using buttons.
- Automatically load and display tasks that match the selected filter.
- See the active filter highlighted in the UI.
You’ll also learn how to connect the filter state to the URL (e.g. /tasks/filter?completed=false), so users can share or bookmark filtered pages.
What You’ll Build in This Unit
When you finish this unit, your app will support a complete filtered workflow.
What the user can do:
- Navigate to the new “Filters” page via the sidebar (
/tasks/filter). - See a filter bar with two buttons: Incomplete and Completed.
- Click a button to switch filters — for example, from
/tasks/filter?completed=false→/tasks/filter?completed=true. - Watch the task list update automatically based on the selected filter.
- See proper loading and error states if the API call takes time or fails.
What you’ll learn:
- How to use URL search parameters (
?completed=true|false) to manage filter state. - How to dynamically fetch filtered data using SWR and your existing API client.
- How to visually indicate the active filter using button variants.
- How to extend your sidebar navigation in
layout.tsxwith new routes.
Learning environment note: Some course workspaces may map
import ... from 'swr'to a simplified local SWR shim for predictable exercises. The patterns you learn still match SWR’s core cache-key andmutate()ideas, but real production apps should use the officialswrpackage and verify behavior around stale data, revalidation, and fetcher errors.
Adding the Filter Tab to the Sidebar
Let’s start by adding a new “Filters” link to your app’s main navigation so users can easily access the filtered task view.
Open src/app/(dashboard)/layout.tsx and update the navItems array:
The rest of the layout logic already handles highlighting active routes:
This means when you’re visiting any URL under /tasks/filter, the “Filters” link will automatically be styled as active.
How this works visually:
The sidebar navigation uses Tailwind CSS to apply different background and text colors to the active route.
As you switch between Dashboard, Tasks, and Filters, users always know which section they’re in.
Now, let’s make the actual filter UI.
Using URL Search Parameters for Filtering
The filter feature depends on URL search parameters — the ?completed=true|false part of the URL.
Example URLs:
/tasks/filter?completed=false→ show only incomplete tasks/tasks/filter?completed=true→ show only completed tasks
This approach has several advantages:
- The filter state is stored in the URL — users can bookmark or share it.
- When the user reloads the page, the selected filter is preserved.
- Your app doesn’t need to store filter state separately in React state — it’s derived directly from the URL.
You’ll use two hooks from Next.js for this:
useRouter()— to change the URL programmatically.useSearchParams()— to read the current value ofcompleted.
Building the Filter Bar UI
Now, let’s create a TaskFilters component that shows the Incomplete and Completed buttons.
File: src/components/tasks/TaskFilters.tsx
Detailed Breakdown
useRouter()
Used to navigate programmatically. When a user clicks a button, we use router.push() to change the URL.
useSearchParams()
Reads the current query string from the URL.
For example, on /tasks/filter?completed=true,
params.get('completed') will return 'true'.
Dynamic Button Styles
The active button uses the primary variant, while the inactive one uses secondary.
This helps users visually see which filter is active.
Rounded Button Styling
The buttons are joined together visually with rounded-l-none and rounded-r-none, forming a two-part toggle bar.
Behavior on Click
Clicking a button updates the URL. Because the filtered task list depends on the URL, this automatically triggers a re-fetch in your SWR logic.
Fetching and Displaying Filtered Tasks
Now let’s build the main filtered page.
File: src/app/(dashboard)/tasks/filter/page.tsx
How It Works
Reading the URL Filter
The useSearchParams() hook extracts the completed value from the query string.
Dynamic SWR Key
The key passed to useSWR() includes the filter:
/api/tasks/filter?completed=${completed}
Every time the completed value changes, SWR automatically re-fetches the data from your backend.
Using the API Client
api.get() performs the actual fetch request.
You don’t need to write separate logic — your reusable API client handles it.
Displaying the List
The results are mapped into TaskRow components, reusing your existing UI for individual tasks.
Error and Loading States
SWR only fills the error value when the fetcher throws. If your apiClient returns an error envelope such as { error, meta }, wrap the fetcher so the envelope becomes a thrown error:
- While fetching → show “Loading filtered tasks…”
- If an error occurs → show a red error message
- If the list is empty → show “No tasks match this filter.”
open_src/components/tasks/TaskFilters.tsx
open_src/app/(dashboard)/tasks/filter/page.tsx
