Recommended resources
The freely available resources this lesson will direct you to.
Hello! Welcome back to our course on Zustand.
In our last lesson, we successfully set up a minimal React project using Vite. This "sandbox" is now our clean slate, ready for us to start exploring Zustand's core features.
Today, we'll write our first piece of Zustand state management code. We will focus on the most fundamental function in the library: create. You will learn how to use it to define a "store," which is a self-contained unit holding both your application's state (the data) and the actions (the functions) that can modify that state.
Learning Outcome: By the end of this lesson, you will be able to create a basic store using the create function, defining both state and actions.
1. The Core of Zustand: The create Function
Everything in Zustand begins with the create function. You import it from the zustand library, and it's the tool you'll use to build all your stores. A store is essentially a hook that you can use in any component to access and manipulate state.
The create function takes a single argument: a "state creator" function. This function is where you define the initial state and the actions that operate on that state. The state creator function, in turn, receives a crucial argument: the set function, which is the primary mechanism for updating your store's state.
To get a formal understanding, let's look at the official documentation.
The official Zustand documentation provides the most precise definition of the create function. Pay attention to the signature and the description of its parameters.
Please read the sections 'create ⚛️', 'Signature', and 'create(stateCreatorFn)'. Focus on understanding that create returns a hook and that the stateCreatorFn you provide receives set, get, and store as arguments. We will focus on set today.
In short, the basic pattern is:const useStore = create((set) => ({ ...your state and actions here... }))
2. Defining the Store's Shape with TypeScript
Given your expertise with TypeScript, we'll follow the best practice of defining the store's structure with an interface before implementing it. This provides type safety, autocompletion, and makes the store's API explicit and easy to understand.
The following video demonstrates this process clearly. It defines a Store type that includes both state properties (todos, newTodo) and action signatures (addTodo, setNewTodo).
Mastering Typescript State using Zustand
This clip from Jack Herrington's 'Mastering Typescript State using Zustand' shows how to define a TypeScript interface for a store before creating it.
Watch from 02:34 to 03:13. Observe how an interface is created to represent the entire store, including both data properties and function signatures for actions.
3. Creating the Store: Initial State and Actions
Now let's put this into practice. We will create our first store in the zustand-sandbox project you set up previously.
- In your project, create a new file:
src/store/counterStore.ts. - We'll start by defining the interface and the initial state.
The video below walks through creating a simple counter store. It demonstrates how to use the create function, pass the TypeScript type, and return an object that represents the initial state of the store.
This segment from 'Zustand - Complete Tutorial' by Cosden Solutions provides a clear, step-by-step guide to creating your first store with an initial state value.
Watch from 02:31 to 04:09. Follow along to see how create is imported and used to define a useCounterStore with an initial count of 0.
Defining Actions
Actions are functions defined within your store that modify the state. They use the set function provided by create. You can think of them as the public API for changing your store's data.
There are two primary ways to use set:
- Passing an object:
set({ count: 0 }). This merges the object into the existing state. It's useful for simple, direct state updates. - Passing a function:
set(state => ({ count: state.count + 1 })). This is the recommended way to update state that depends on its previous value. Zustand passes the current state to your function, and you return a new object with the updated values. This pattern avoids race conditions and is analogous to the functional update form of React'suseState.
The next part of the tutorial clearly demonstrates how to add actions to the store to increment and decrement the count.
Continuing with the same tutorial, this part shows how to implement actions using the set function, including the crucial pattern of using a function to access the previous state.
Watch from 05:39 to 09:48. Pay close attention to the transition from a hardcoded set({ count: 1 }) to the safer set(state => ({ count: state.count + 1 })) pattern.
For a textual reinforcement of this concept, the article "How to use Zustand" provides a concise explanation of actions.
This article by Refine provides a good summary of what actions are and how they use the set function.
Read the short subsection 'What are actions?' to solidify your understanding. Then, review the subsection 'Updating the whole state' which reinforces the two ways of using the set function.
4. Your First Zustand Store
Now, let's combine these concepts and write the full code for our counterStore. Add the following content to your src/store/counterStore.ts file.
import { create } from 'zustand';
// 1. Define the interface for the store's state and actions
interface CounterState {
count: number;
increment: () => void;
decrement: () => void;
reset: () => void;
}
// 2. Create the store using the `create` function
export const useCounterStore = create<CounterState>((set) => ({
// Initial state
count: 0,
// Actions
increment: () => set((state) => ({ count: state.count + 1 })),
decrement: () => set((state) => ({ count: state.count - 1 })),
reset: () => set({ count: 0 }),
}));
Let's break down this code:
- We define the
CounterStateinterface, making the shape of our store explicit. - We call
create<CounterState>to create a typed store. - The function passed to
createreturns an object. count: 0is our initial state.incrementanddecrementare actions that use the functionalsetpattern because their logic depends on the previouscount.resetis an action that uses the objectsetpattern for a simple, direct update.
You have now successfully created your first Zustand store! In the next lesson, we will learn how to connect this store to a React component to read the state and call these actions.
Conclusion
In this lesson, we took a major step into the world of Zustand. You've learned the most critical concepts for getting started:
createis the central function for building stores.- A store is an object containing both state (the data) and actions (functions to modify the data).
- Using TypeScript interfaces to define the store's shape is a best practice for robust applications.
- Actions use the
setfunction to update state, preferably with an updater function (set(state => ...)) for state transitions that depend on previous values.
Next Lesson Preview:
In our next lesson, we will learn how to access state and actions from the store within a React component using the generated hook. We will bring our counterStore to life by displaying the count and wiring up buttons to our increment, decrement, and reset actions.