Recommended resources
The freely available resources this lesson will direct you to.
Hello! Let's dive into our next lesson on Zustand.
In our last session, we explored the set function in detail, focusing on the difference between its default shallow-merging behavior and the option to completely replace state. We also saw how to use the updater function, set(state => ...), to safely perform updates based on the previous state.
Today, we'll build on that by learning how to access the current state from within an action using the get function for complex state transitions. While the set updater is great for simple transitions, get provides more flexibility for actions that involve conditional logic or depend on multiple pieces of state.
By the end of this lesson, you'll understand when and how to use get() inside your store, as well as its counterpart getState() for accessing state from outside the store.
1. Introducing the get Function
When you define your store with create, the function you provide receives two arguments: set and get.
import { create } from 'zustand';
const useStore = create((set, get) => ({
// ... your state and actions
}));
We've already worked extensively with set. The get function is its partner; it allows you to read the current state from anywhere inside your store's definition, particularly within your actions.
Let's look at a very simple use case.
Zustand. React state management set up in a few minutes
This article from the TSH blog provides a concise example of using get for an action that only reads state without modifying it.
Please read the section titled 'Get() the state value'. Notice how the create function is updated to accept get as a parameter, and how get() is used within the logNumber action.
As you saw, get() is called like a function, and it returns the entire state object. You can then access any property from it, like get().number. This is useful for actions that need to read state to perform some side effect, like logging, without necessarily updating the state.
2. get() for Complex State Transitions
The real power of get shines when you need to compute a new state based on existing values, especially when there's logic involved before the final set call.
Consider an action that needs to find a specific item in an array from the state, modify it, and then update the state. Using get allows you to cleanly separate the "read" phase from the "write" phase.
The following resource provides an excellent, practical example with a library store.
React State Management — using Zustand
This Medium article by Globant demonstrates a bookStore with actions for issuing and returning books. It's a perfect example of a complex state transition.
In the 'Create a store' section, examine the bookStore.js code snippet. Pay close attention to the issueBook and returnBook actions. See how const books = get().books; is used to retrieve the current list of books before any mapping or updating occurs.
In the issueBook action, the logic is:
- Read: Get the current array of books using
get().books. - Compute: Create a
updatedBooksarray by mapping over the original books and changing the status of the relevant one. - Write: Call
setwith the new books array and updated counters.
This pattern is very common. You use get() to fetch the necessary data, perform your business logic, and then use set() to commit the final state.
3. get() vs. set(state => ...): When to Use Which?
At this point, you might wonder when to use get() versus the set(state => ...) updater function we learned about previously. Both give you access to state. Here’s a guide to help you decide:
| Method | set(state => ({...})) |
get() |
|---|---|---|
| Primary Use Case | Simple, atomic updates based on the immediate previous state. | Complex actions with conditional logic or dependencies on multiple state values. |
| Syntax | set(state => ({ count: state.count + 1 })) |
const currentCount = get().count; if (currentCount < 10) { set({ count: currentCount + 1 }) } |
| When to Prefer | When the update logic is self-contained and directly derives the next state from the previous one (e.g., incrementing, toggling). | When you need to read state to make a decision before you know what the new state will be, or if an action might not update the state at all. |
Let's consider a game character's special attack, which should only be possible if they have enough mana.
const usePlayerStore = create((set, get) => ({
health: 100,
mana: 50,
specialAttackCost: 75,
performSpecialAttack: () => {
// Read multiple state values using get()
const currentMana = get().mana;
const cost = get().specialAttackCost;
// Perform conditional logic
if (currentMana >= cost) {
console.log("Performing special attack!");
// Only call set() if the condition is met
set(state => ({
mana: state.mana - cost
}));
} else {
console.log("Not enough mana!");
// No state update occurs
}
}
}));
In this example, get() is the ideal tool because we need to check a condition (currentMana >= cost) before we can even decide to call set.
4. Accessing State Outside the Store: getState()
The get function is only available inside the create block. But what if you need to access the store's state from a utility function, or from somewhere outside a React component?
For this, Zustand exposes a getState() method directly on the store hook itself.
Crucially, getState() is non-reactive. This means calling it will give you a snapshot of the state at that moment, but it will not cause your component or function to re-render or re-run when the state changes later.
This video explains the concept clearly and shows a practical use case.
React Zustand Tutorial - 8. setState, getState and separate actions from the store
This tutorial from Conny Gu explains the non-reactive nature of getState() and contrasts it with the reactive hooks we've been using.
Please watch from 04:13 to 08:26. The first part (until 06:16) demonstrates why getState() is non-reactive. The second part shows a great practical use case: initializing a component's local useState with a value from the Zustand store.
As the video showed, useFoodStore.getState().fish gives you the current value but doesn't subscribe to updates. This makes it perfect for one-off reads, like setting an initial value, without creating an unnecessary subscription.
The same video (from 08:08 onwards) also introduces an advanced pattern where actions are defined outside the store, using getState() and setState() to interact with it. This can lead to cleaner code and better code-splitting. While you don't have to use this pattern, it's a powerful demonstration of what getState() enables.
Conclusion
Today we've added another essential tool to your Zustand toolkit. Let's recap the key takeaways:
get(): The second argument increate((set, get) => ...). Use it inside actions to read the current state for complex logic, conditional updates, or when an update depends on multiple state values.set(state => ...): Best for simple, atomic updates where the new state is a direct transformation of the old state (e.g.,count: state.count + 1).store.getState(): A method on the created hook (useStore.getState()) for accessing state from outside the store definition and outside React components. It is non-reactive and provides a snapshot of the current state.
You now have a comprehensive understanding of the different ways to read and write state in Zustand.
In our next lesson, we will tackle a very common requirement in modern applications: implementing asynchronous actions for fetching and managing remote data. You'll see how the set and get functions we've learned are used to handle loading states and update the store once data arrives from an API.