- Lab
- Core Tech

Guided: State Management with Zustand
This Guided Code Lab will teach you how to manage state in React applications using Zustand. By building a functional book collection application, you'll learn to create, manipulate, and persist state with Zustand.

Path Info
Table of Contents
-
Challenge
Introduction
Welcome to the lab Guided: State Management with Zustand.
Zustand is a small, fast, and flexible state management library for React. It's designed to be easy to use while providing powerful features for managing global state in your applications. Zustand is unopinionated, meaning it doesn't enforce a particular pattern or structure, allowing you to choose the best approach for your needs.
In this lab, you'll gain hands-on experience with Zustand by building a practical book collection application with features like adding books, updating library details, clearing books, logging books, and persisting data. You'll learn how to create stores, manipulate state, work with nested objects, combine slices, and use middleware to persist state. ---
Familiarizing with the Program Structure
The application includes the following files:
App.js
: The main React component that brings together other components, such asLibraryDetails
andBookList
.store.js
: The JavaScript file where the Zustand store is defined.AddBook.js
: A React component that allows users to add new books to the collection.BookList.js
: A React component that displays the list of books.LibraryDetails.js
: A React component that allows users to view and update the library details.
You will focus on working with the
store.js
file to gradually implement the necessary 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 namedstep2
,step3
, 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.,store-2-1.js
instep2
), where[file]
represents the file name, the first number indicates the step, and the second represents the task. -
Challenge
Creating a Store
To create a store with Zustand, you use the
create
function. This function initializes the store with an initial state and optionally includes actions for updating the state.Here's a basic example of how to create a store:
import { create } from 'zustand'; // Create a store with an initial state const useStore = create((set) => ({ dogs: 0, // Initial state addDog: () => set((state) => ({ dogs: state.dogs + 1 })), // Action to update state }));
This example shows a store with an initial state containing a
dogs
property set to0
. It also defines an actionaddDog
to update the state by incrementing thebears
property.Once you have created a store, you can fetch the entire state, but be careful as the following will cause the component to update on every state change:
const state = useStore();
To make your components more efficient, you can select specific slices of the state. Zustand detects changes with strict-equality (
old === new
) by default, which is efficient for atomic state picks:const dogs = useStore((state) => state.dogs); const addDog = useStore((state) => state.addDog);
This example selects the
dogs
state and theaddDog
action separately. This ensures that the component only re-renders if thedogs
state or theaddDog
action changes.After creating the store and selecting the state slices, you can use them in your React components. For example:
const DogCounter = () => { const dogs = useStore((state) => state.dogs); const addDog = useStore((state) => state.addDog); return ( <div> <h1>{dogs} dogs around here ...</h1> <button onClick={addDog}>Add a dog</button> </div> ); };
This component accesses the
dogs
state and theaddDog
action from the store and use them to display the number of dogs and add a new dog when the button is clicked.In the next tasks, you'll start implementing a store for managing books and use it in your components.
-
Challenge
Working with State
To manipulate state in your Zustand store, you can use the
set
andget
methods.The
set
MethodThe
set
method is used to update the state in the store. It accepts a function that receives the current state and returns the new state. Zustand will then merge the returned state with the existing state.Here's an example of how to use this method:
const useStore = create((set) => ({ dogs: 0, // Initial state addDog: () => set((state) => ({ dogs: state.dogs + 1 })), // Action to update state }));
In this example, the
addDog
action uses theset
method to increment thedogs
property in the state.By default, Zustand performs shallow merging when updating the state. However, you can disable this behavior by passing
true
as the second argument to theset
method. When this is done, Zustand will replace the entire state with the new state returned by the function.Default Behavior (Shallow Merging):
// The "dogs" property is merged with the other properties in the state set((state) => ({ dogs: state.dogs + 1 }));
Replacing Entire State:
// The state now consists of only the "dogs" property set((state) => ({ dogs: state.dogs + 1 }), true);
In most cases, the default shallow merging behavior is preferred, as it simplifies state updates and reduces the risk of accidentally overwriting the entire state.
The
get
MethodThe
get
method is used to retrieve the current state from the store. This can be useful when you need to perform actions that depend on the existing state.Here's an example of how to use the
get
method:const useStore = create((set, get) => ({ dogs: 0, // Initial state addDog: () => set((state) => ({ dogs: state.dogs + 1 })), logDogs: () => { console.log(get().dogs); // Retrieve the current state }, }));
In this example, the
logDogs
action uses theget
method to log the current number of dogs to the console.You're ready to implement actions in the Zustand store to manage the state of books in your application. You will start with the
addBook
action. This action should use theset
function to allow users to add books to the store. It's time for theupdateBook
action. This action should use theset
function to update a given book in the store. You will now add theclearBooks
action. This action should use theset
function to reset thebooks
array. Now you can implement thelogBooks
action. This action should use theget
function to log thebooks
array to the console. -
Challenge
Using Nested Objects
When dealing with nested objects in state management, one common issue is how to update a deeply nested property without overwriting the entire object. For example, consider the following state structure:
const useStore = create((set) => ({ user: { profile: { name: 'John Doe', age: 30, }, settings: { theme: 'dark', notifications: true, }, }, }));
If you want to update the user's name, you need to ensure that the rest of the
user
object remains unchanged. A naive update might overwrite other parts of theuser
object.Zustand allows you to update nested objects by merging the new state with the existing state. This is achieved by using the
set
function and spreading the current state to retain unchanged properties.Here's how you can update the user's name while keeping the rest of the
user
object intact:const useStore = create((set) => ({ user: { profile: { name: 'John Doe', age: 30, }, settings: { theme: 'dark', notifications: true, }, }, // Action to update the user's name updateUserName: (newName) => set((state) => ({ user: { ...state.user, // Keep other parts of the user state unchanged profile: { ...state.user.profile, // Keep other parts of the profile unchanged name: newName, // Update the name property }, }, })), }));
In this example, the
updateUserName
action updates thename
property within theprofile
object. Notice how the rest of theuser
object, includingsettings
, remains unchanged. This is achieved by spreading thestate.user
andstate.user.profile
objects.In summary:
- Use the spread operator (
...
) to merge the new state with the existing state, ensuring that unchanged properties are preserved. - Zustand performs shallow merging by default, so when updating nested objects, you need to manually merge the nested properties.
- Zustand's approach allows you to update deeply nested properties without overwriting the entire state.
Now you're ready to implement a store that includes nested objects and actions to update them.
- Use the spread operator (
-
Challenge
The Slices Pattern
While having one store is often recommended for simplicity, there are cases where dividing your store into smaller, modular slices can be beneficial, especially as your application grows in complexity.
The slices pattern involves breaking down a large store into smaller, more manageable slices. Each slice is responsible for a specific part of the state and its associated actions. This approach promotes modularity and makes the state management easier to maintain and scale:
- Modularity: By dividing the state into smaller slices, each slice can focus on a specific aspect of the application. This makes the code more modular and easier to understand.
- Maintainability: Smaller slices are easier to maintain and update. Changes to one slice are less likely to impact other slices.
- Scalability: As your application grows, managing a single large store can become cumbersome. Slices allow you to scale your state management more effectively.
To create a slice, define a function that takes
set
(and optionallyget
) as arguments and returns an object with the initial state and actions for that slice. Here's an example of two slices:// Slice for managing dogs-related state and actions. const dogsSlice = (set, get) => ({ dogs: [], // Initial state: an empty array of dogs. // Action to add a new dog to the dogs array. addDog: (dog) => set((state) => ({ dogs: [...state.dogs, dog], })), // Action to clear all dogs. clearDogs: () => set({ dogs: [] }), }); // Slice for managing cats-related state and actions. const catsSlice = (set) => ({ cats: [], // Initial state: an empty array of cats. // Action to add a new cat to the cats array. addCat: (cat) => set((state) => ({ cats: [...state.cats, cat], })), // Action to clear all cats. clearCats: () => set({ cats: [] }), });
After defining the slices, you can combine them into a single store using the
create
function. Spread the slices into the store definition to include their state and actions:import { create } from 'zustand'; // Creating the Zustand store that combines both slices (dogs and cats). const useStore = create((set, get) => ({ ...dogsSlice(set, get), // Include the dogs slice in the store. ...catsSlice(set), // Include the cats slice in the store. }));
In this example, both the
dogs
andcats
slices are combined into a single store. The combined store includes the state and actions from both slices, allowing you to manage the dogs and cats state separately, but within the same store.You're now ready to implement the slices pattern in your application.
-
Challenge
Using Middleware
Middleware in Zustand is a way to extend the functionality of your stores. It allows you to intercept and modify actions and state updates, adding additional behavior before or after the state changes. Middleware can be used for various purposes, such as:
- Logging: Logs every state change to the console, which is useful for debugging and understanding how state changes over time.
- Persistence: Saves the state to local storage or another storage mechanism, allowing it to persist across sessions.
- Integration with other libraries: For example, you can use the Immer middleware to make updating deeply nested objects more convenient.
One of the most commonly used middleware in Zustand is the
persist
middleware. It allows you to save the state of your store to a storage mechanism, such aslocalStorage
orsessionStorage
, and automatically restore the state when the application is reloaded.To use the
persist
middleware, you need to wrap your store definition with thepersist
function. Thepersist
function takes two arguments:- The Store Definition: A function that defines the initial state and actions of your store.
- The Configuration Object: An object that specifies how the state should be persisted.
Here's an example of how to use the
persist
middleware:import { create } from 'zustand'; import { persist } from 'zustand/middleware'; // Define the store const useStore = create( persist( (set, get) => ({ // Initial state and actions here items: [], addItem: (item) => set((state) => ({ items: [...state.items, item], })), }), { name: 'my-storage', // The key used to save the state in localStorage getStorage: () => localStorage, // Optional, by default, 'localStorage' is used } ) );
In this example:
- The store is defined with an initial state (
items
) and an action (addItem
). - The
persist
function wraps the store definition. - The configuration object specifies the key (
name
) used to save the state inlocalStorage
and the storage mechanism (getStorage
).
You're now ready to implement the
persist
middleware in your application. -
Challenge
Conclusion
Congratulations on successfully completing this Code Lab!
To run the application, either click the Run button 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.
This lab covers just the fundamentals of Zustand. For more information, read the Zustand documentation and guides. ---
Extending the Program
Consider exploring these ideas to further enhance your skills and expand the capabilities of the program:
-
Dark/Light Mode Toggle: Add a dark/light mode toggle to the application. Manage the theme state in the Zustand store.
-
Filtering and Sorting: Add filtering and sorting functionalities to the book list. Allow users to filter books by author or title and sort them by different criteria (e.g., title, author).
-
Search Functionality: Implement a search feature that allows users to search for books by title or author. Update the state to reflect the search results.
-
Pagination for Book List: Implement pagination for the book list. Add controls to navigate between different pages of books and update the state management logic to load a specific page of books based on the selected page number. ---
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.