- Lab
- Core Tech

Guided: State Management with SWR
This code lab will teach you how to implement efficient state management in React applications using SWR. You'll learn to fetch and update data, handle errors, and implement advanced features like infinite scrolling. By the end of this lab, you'll have practical experience in building web applications with optimized data handling.

Path Info
Table of Contents
-
Challenge
Introduction
Welcome to the lab Guided: State Management with SWR.
SWR is a React Hooks library created by the team at Vercel to handle data fetching, caching, and revalidation in React applications. SWR stands for Stale-While-Revalidate, an HTTP cache invalidation strategy defined by HTTP RFC 5861.
The core concept behind SWR is to first return cached (stale) data while revalidating it in the background, ensuring that the UI always displays up-to-date information. SWR abstracts away many of the complexities involved in state management, data fetching, and revalidation, allowing you to focus on building features instead of worrying about state consistency.
While React has several options for managing state, SWR excels in scenarios where the state is tied closely to remote data. It's especially useful for applications that need to reflect data from a backend API, offering a high-level solution that combines fetching, caching, and revalidating into a simple, intuitive API.
In this lab, you'll dive into the fundamentals of SWR by implementing state management in a simple notes manager application. You'll learn how to:
- Fetch and display data using the
useSWR
hook. - Handle errors and loading states gracefully.
- Implement real-time data updates using SWR's revalidation features.
- Manage paginated data with infinite scrolling using the
useSWRInfinite
hook.
The application allows users to view a list of existing notes, which can be toggled between a standard list and an infinite-scrolling list. To add a new note, users will fill out a form by entering a title and content. Upon form submission, the note will be added to the list displayed on the page. Users can delete notes by clicking a delete button next to each note.
By the end of this lab, you'll have a solid understanding of how to use SWR for efficient state management in your React applications. ### Familiarizing with the Program Structure
The application includes the following files:
- App.js: The main React component that structures the application.
- NoteForm.js: A React component for adding new notes.
- NotesList.js: A React component for displaying all the available notes in a standard list.
- NotesInfiniteList.js: A React component for displaying notes with infinite scrolling.
- api.js: A file containing simulated API functions for note operations.
The standard and the infinite-scrolling lists are different components because there are significant differences in their implementations.
You'll focus on the
NotesList.js
,NoteForm.js
, andNotesInfiniteList.js
files, implementing the missing functionality.Start by examining the provided code to understand the program structure. Once you're ready, begin coding. If you run into any issues, a
solution
directory is available for reference. It contains subdirectories namedstep1
,step2
, and so on, each corresponding to a step in the lab. Within each subdirectory, solution files follow the naming convention[file]-[step number]-[task number].js
(e.g.,NotesList-1-1.js
instep1
), where[file]
represents the file name, the first number indicates the step, and the second represents the task. - Fetch and display data using the
-
Challenge
Step 1: Basic Data Fetching with SWR
Using the
useSWR
HookAt the core of SWR is the
useSWR
hook, which provides an API for handling data fetching, caching, and revalidation.The
useSWR
hook takes at least two arguments, a key and a fetcher function:-
Key: The first argument is a unique key that identifies the data. This key helps SWR to cache and manage the data effectively. It's commonly a string representing the endpoint or resource you are fetching.
-
Fetcher Function: The second argument is a function that performs the actual data fetching. This function can use
fetch
,axios
, or any other method to retrieve data from an API.
The hook returns an object containing the following properties:
data
: The fetched data.error
: Any error encountered during the fetch.isLoading
: Aboolean
indicating if there's an ongoing request and no loaded data. Fallback data and previous data are not considered loaded data.isValidating
: Aboolean
indicating if there's a request or revalidation loading.mutate(data?, options?)
: A function to mutate the cached data.
Here's an example to illustrate the usage of the
useSWR
hook:import useSWR from 'swr'; // Define a fetcher function const fetcher = async (url) => { const response = await fetch(url); if (!response.ok) { throw new Error('Failed to fetch data'); } return response.json(); }; function ExampleComponent() { // Use the useSWR hook to fetch data const { data, error, isValidating } = useSWR('/api/data', fetcher); // Handle error state if (error) return <div>Error: {error.message} // Handle loading state if (isValidating) return <div>Loading...</div>;</div>; // Render the fetched data return ( <div> <h1>Fetched Data</h1> <pre>{JSON.stringify(data, null, 2)}</pre> </div> ); }
In this example:
- The
fetcher
function usesfetch
to retrieve data from the/api/data
endpoint and returns the JSON response. - The
useSWR
hook is called with the key/api/data
and thefetcher
function. - The
useSWR
hook returns an object with a property named data. By default, data would be accessed directly by its name. - The component handles the loading state by checking
isValidating
and displaying a loading message. - It handles the error state by checking
error
and displaying an error message. - Finally, it renders the fetched data when available.
When you use
useSWR
, the hook automatically handles several critical aspects for you:- Caching: SWR caches the fetched data based on the key, ensuring that subsequent requests for the same key return the cached data instantly.
- Revalidation: SWR revalidates the data in the background to keep it up-to-date, fetching new data whenever the component re-renders or the key changes.
- Error Handling: SWR provides a built-in mechanism for handling errors, allowing you to define custom error handling logic.
Now that you understand how
useSWR
works, you're ready to fetch the notes from the simulated API in the application. TheuseSWR
hook will fetch data and store the fetched notes in thenotes
variable to display them.Additionally, you can show a message while loading the data. You'll do that in the next task by using the
isValidating
property (to know when a revalidation is happening). -
-
Challenge
Step 2: Handling Errors
Error Handling in SWR
Error handling is an important aspect of data fetching, and SWR provides built-in mechanisms to handle errors gracefully. When using
useSWR
, you can add two specific callback functions to handle errors:-
onError(err, key, config)
: This callback is triggered when a request returns an error. It provides the error object, the key used for the request, and the SWR configuration. -
onErrorRetry(err, key, config, revalidate, revalidateOps)
: This callback handles error retries. It provides additional arguments, including a function to trigger revalidation and an object containing retry options (e.g., the retry count).
Also,
useSWR
returns anerror
object. This object contains the error thrown by the fetcher function, or it isundefined
if no error occurred.Here's an example that illustrates error handling in SWR:
import useSWR from 'swr'; const fetcher = async (url) => { const response = await fetch(url); if (!response.ok) { throw new Error('Failed to fetch data'); } return response.json(); }; function ExampleComponent() { const { data, error, isValidating } = useSWR('/api/data', fetcher, { onErrorRetry: (error, key, config, revalidate, revalidateOps) => { // Retry up to 2 times if (revalidateOps.retryCount >= 2) return; // Retry after 5 seconds setTimeout(() => revalidate(revalidateOps), 5000); } }); if (error) return <div>Error: {error.message}</div>; if (isValidating) return <div>Loading...</div>; return ( <div> <h1>Fetched Data</h1> <pre>{JSON.stringify(data, null, 2)}</pre> </div> ); }
In this example:
- The
onErrorRetry
callback retries the request up to 2 times, with a 5-second delay between retries. - The
error
object is checked to display an error message if an error occurs during data fetching.
Now that you understand how to handle errors with
useSWR
, you're ready to apply this knowledge in the next tasks. -
-
Challenge
Step 3: Mutations and Revalidation
Mutations and Revalidation in SWR
Mutations and revalidation allow you to update and synchronize the local cache with the remote data source efficiently.
A mutation in SWR refers to updating the local data cache. This is particularly useful when you need to update the UI optimistically before the actual data operation (like adding, updating, or deleting data) is confirmed by the backend. The
useSWRConfig
hook provides amutate
function that you can use to update the cache for a specific key.For example, if you want to update the cache for a list of items after adding a new item, you can use the
mutate
function to trigger a revalidation and fetch the updated list from the server.Revalidation ensures that the data displayed in your application is always up-to-date by marking the data as expired and triggering a refetch. In the previous step, you used the
revalidate
function in theonErrorRetry
callback to retry fetching data after an error. Similarly, you can use themutate
function to revalidate data after performing a mutation. This helps keep the UI in sync with the latest data.Here's an example to illustrate how to use mutations and revalidation in SWR:
import useSWR, { useSWRConfig } from 'swr'; const fetcher = async (url) => { const response = await fetch(url); if (!response.ok) { throw new Error('Failed to fetch data'); } return response.json(); }; function ExampleComponent() { const { data, mutate } = useSWR('/api/data', fetcher); const { mutate: globalMutate } = useSWRConfig(); const handleAddItem = async () => { // Optimistically update the cache await globalMutate('/api/data', (data) => [...data, newItem]); // Make the actual API call await fetch('/api/data', { method: 'POST', body: JSON.stringify(newItem), }); // Revalidate the cache to ensure data consistency await globalMutate('/api/data'); }; return ( <div> <h1>Fetched Data</h1> <pre>{JSON.stringify(data, null, 2)}</pre> <button onClick={handleAddItem}>Add Item</button> </div> ); }
In this example:
- The
useSWRConfig
hook provides a globalmutate
function for updating the cache. - The
useSWR
hood also returns amutate
function to mutate the cached data. It is functionally equivalent to the globalmutate
function fromuseSWRConfig
but does not require thekey
parameter. - The
mutate
function is used to optimistically update the cache (updating the local data manually while waiting for the remote mutation to finish) and trigger revalidation after performing an API call. - The
globalMutate
function ensures that the cache is consistent with the latest data from the server.
These are the parameters of the
mutate
function:key
: Same asuseSWR
's key, but if it's a function, it behaves as a filter function.data
: (Optional) Data to update the client cache, or an async function for the remote mutation.options
: (Optional) Accepts the following options:optimisticData
: Data to immediately update the client cache, or a function that receives current data and returns the new client cache data, usually used in optimistic UI.revalidate = true
: Should the cache revalidate once the asynchronous update resolves. If set to a function, the function receives data and key.populateCache = true
: Should the result of the remote mutation be written to the cache, or a function that receives new result and current result as arguments and returns the mutation result.rollbackOnError = true
: Should the cache rollback if the remote mutation errors, or a function that receives the error thrown from fetcher as arguments and returns a boolean whether should rollback or not.throwOnError = true
: Should the mutate call throw the error when fails.
Now that you understand the concepts of mutation and revalidation, you're ready to apply this knowledge in the next tasks. After completing the task, the SWR cache will be updated after adding a new note.
However, you also need to revalidate the cache after deleting a note. After completing the task, the SWR cache will be updated after deleting a note.
- The
-
Challenge
Step 4: Infinite Loading
Infinite Loading and Pagination in SWR
In SWR, you can implement both normal pagination and infinite loading as mechanisms to handle large sets of data efficiently. Infinite loading is particularly useful for applications where you want to load more data as the user scrolls down the page, providing a seamless user experience.
To implement infinite pagination, SWR provides the
useSWRInfinite
hook. This hook allows you to fetch data in pages and manage state for paginated data. TheuseSWRInfinite
hook can take three arguments:getKey
: A function that accepts the index and the previous page data, returns the key of a page.fetcher
: A function that performs the actual data fetching for each page (same asuseSWR
's fetcher function).options
: Accepts all the options that useSWR supports, with 4 extra options:initialSize = 1
: Number of pages should be loaded initially (it's is not allowed to change after set).revalidateAll = false
: Always try to revalidate all pages.revalidateFirstPage = true
: Always try to revalidate the first page.persistSize = false
: Don't reset the page size to 1 (orinitialSize
if set) when the first page's key changes.parallel = false
: Fetches multiple pages in parallel.
The
useSWRInfinite
hook returns an object containing these values:data
: An array of fetch response values of each page.error
: Same asuseSWR
's error.isLoading
: Same asuseSWR
'sisLoading
.isValidating
: Same asuseSWR
'sisValidating
.mutate
: Same asuseSWR
's boundmutate
function but manipulates the data array.size
: The number of pages that will be fetched and returned.setSize
: Set the number of pages that need to be fetched.
Here's an example to illustrate the usage of
useSWRInfinite
:import useSWRInfinite from 'swr/infinite'; const fetcher = async (url) => { const response = await fetch(url); if (!response.ok) { throw new Error('Failed to fetch data'); } return response.json(); }; function ExampleComponent() { const { data, error, size, setSize, mutate, isValidating, isLoading, } = useSWRInfinite( (index) => `/api/data?page=${index + 1}`, // Key function fetcher // Fetcher function ); if (isLoading) return <div>Loading...</div>; if (error) return <div>Error: {error.message}</div>; return ( <div> {data.map((page, i) => ( <div key={i}> {page.map(item => ( <div key={item.id}>{item.name}</div> ))} </div> ))} <button onClick={() => setSize(size + 1)}>Load More</button> </div> ); }
In this example:
- The key function generates a unique key for each page based on the page number.
- The fetcher function fetches data for each page.
- The
setSize
function is used to load more pages.
When you need to revalidate the data globally (for instance, after adding a new item), you have to use the
unstable_serialize
function fromswr/infinite
. This function serializes the key function used inuseSWRInfinite
so you can revalidate the cache globally.This approach is necessary when the mutation and revalidation are performed in a different component from the one that fetched the data.
Here's how you can use
unstable_serialize
:import { useSWRConfig } from "swr"; import { unstable_serialize } from "swr/infinite"; function App() { const { mutate } = useSWRConfig(); mutate(unstable_serialize((index) => `/api/data?page=${index + 1}`)); }
This example shows how to use
unstable_serialize
to revalidate the paginated data globally. Note thatunstable_serialize
is not a stable API and might change in the future. For the latest information on this API, refer to the SWR documentation on pagination.Now that you understand how infinite pagination works in SWR, you're ready to apply this knowledge in the next tasks.
-
Challenge
Conclusion
Congratulations on successfully completing this Code Lab!
This lab covers just the fundamentals of SWR. For more information, read the guides on the SWR documentation page.
To run the application, either click the Run in the bottom-right corner of the screen or in a Terminal tab execute:
npm start
Then, click the following link to open the application in a new browser tab: {{localhost:3000}}. Alternatively, you can refresh the Web Browser tab to reload and view the application there.
For the standard list, the method
getNotes
from the simulated API (api.js
) simulates a 30% chance of failure to demonstrate error handling, so sometimes, when getting the notes (by refreshing the page or by SWR revalidating the notes), you'll get an error message, but the application should retry automatically to load the notes correctly. ---Extending the Program
Consider exploring these ideas to further enhance your skills and expand the capabilities of the program:
-
Normal Pagination in Standard List: Implement normal pagination in the standard notes list. Add controls to navigate between different pages of notes and update the data fetching logic to load a specific page of notes based on the selected page number.
-
Optimistic Updates: Implement optimistic updates when adding or deleting notes. Update the UI immediately to reflect the changes before the server confirms them. If the server operation fails, revert the changes in the UI.
-
Note Editing: Implement functionality to edit existing notes. Add an edit button next to each note that allows users to modify the title and content. Update the note in the backend and revalidate the notes list to reflect the changes. ---
Related Courses on Pluralsight's Library
If you're interested in further honing your React skills or exploring more topics, Pluralsight offers several excellent courses in the following path:
These courses cover many aspects of React programming. Check them out to continue your learning journey in React!
-
What's a lab?
Hands-on Labs are real environments created by industry experts to help you learn. These environments help you gain knowledge and experience, practice without compromising your system, test without risk, destroy without fear, and let you learn from your mistakes. Hands-on Labs: practice your skills before delivering in the real world.
Provided environment for hands-on practice
We will provide the credentials and environment necessary for you to practice right within your browser.
Guided walkthrough
Follow along with the author’s guided walkthrough and build something new in your provided environment!
Did you know?
On average, you retain 75% more of your learning if you get time for practice.