Lorenz Weiß Hi, I'm Lorenz, a frontend-focused web developer. I'm in love with the internet and its people and interested in everything related to it.

Managing React state with Zustand

7 min read 1965

Managing React State With Zustand

Editor’s note: This article was updated on 14 February 2022 to remove any outdated information and add the Using Zustand to persist state section.

To manage state in modern frontend frameworks, Redux has always been king. But now, many new competitors are entering the fray with new ideas, desperate to overthrow Redux with the promise of ease-of-use and simplicity.

I, for one, am excited about the growing number of new ways to manage your state. In this article, I’ll cover the simplest and smallest of all: Zustand.

You’ll learn what Zustand is, how it differs from other modern tools like Jotai or Recoil, and when you should use it over Redux; in all, we’ll cover the following:

What is Zustand?

First of all, I’m not claiming that Zustand is currently the best tool to use. As in most cases, the question of which tool is the best cannot really be answered, or at least it must be answered with the dreaded phrase, “It depends.”

Tweet That Reads, "Becoming A Senior Engineer Is Just Saying "It Depends" Over And Over Again Until You Quit/Retire"

To get the full picture of Zustand, let’s go over some of the details of the library, how it is positioned in the market, and compare it to other libraries.

Zustand was created and is maintained by the creators of react-spring, react-three-fiber, and many other awesome tools, Poimandres. At 1.5kB, it’s probably the smallest library of all—you can read through the source code in a matter of minutes.

Getting started with Zustand

Zustand is known for its simplicity. On the (really beautiful) website they created for their package, you can see a very simple example written in just four lines of code that creates a globally available state:

import create from 'zustand'

const useStore = create(set => ({
  bears: 0,
  increasePopulation: () => set(state => ({ bears: state.bears + 1 })),
}))

The main function of the state management library is called create. It accepts a callback function as its first argument, which in turn accepts a set function that should be used when manipulating memory.



The function create then returns another function, which in our case, is called useStore. As you can see from the name of the return value, it returns a Hook, so you can insert it anywhere in your React application, like so:

>function BearCounter() {
  const bears = useStore(state => state.bears)
  return <h1>{bears} around here ...</h1>
}

Wherever this Hook is injected and the state used, the component will rerender when the state changes, making it a fully functional global state with these small lines of code.

You can also extract the action, which changes the state anywhere you want from the same Hook like this:

function Controls() {
  const increasePopulation = useStore(state => state.increasePopulation)
  return <button onClick={increasePopulation}>one up</button>
}

But, what about performing async actions or fetching something from a server that you save to your store?

Well, you can make your mutation function asynchronous and Zustand will set your state when it’s ready. That way you don’t need to worry about asynchronous functions inside your component anymore:

const useStore = create(set => ({
  fishies: {},
  fetch: async pond => {
    const response = await fetch(pond)
    set({ fishies: await response.json() })
  }
}))

State management can’t be simpler, right? But it looks very similar to other modern tools like Jotai or Recoil, you say? It may seem that way, but let’s look at some of the differences between these libraries.

Using Zustand to persist state

One of the most common use cases for using a global state management tool is that you want to persist your state over the lifecycle of your website. For example, if you create a survey tool, you want to save the user’s answers and state.


More great articles from LogRocket:


Now, if the user accidentally reloads your page, all the answers and the pointer would be lost. This is a common use case where you want to persist this exact state.

That is, even if the user reloads the page or closes the window, the responses and state are retained and can be restored when the user visits the site again.

Zustand solves this particular use case with a nice “battery-included” middleware called persist that persists your store in any way you want. The only thing you need to do to persist your state in your application’s sessionStorage is to add the following:

import create from "zustand"
import { persist } from "zustand/middleware"

export const useStore = create(persist(
  (set, get) => ({
    anwers: [],
    addAnAnswer: (answer) => set((prevState) => (
      { answers: [...prevState.answers, answer] }
    ))
  }),
  {
    name: "answer-storage", // unique name
    getStorage: () => sessionStorage, // (optional) by default the 'localStorage' is used
  }
))

As you can see, the store is exactly the same as without the persistence. The only difference is that it is additionally wrapped with persist middleware. You also need to give the store a unique name to identify it in the browser.

Optionally, you can also decide which persistence you want; by default, this is localStorage, but you can also choose sessionStorage if you want the state to be persistent only within the user’s session.

Is Zustand better than Redux?

Redux is probably still the most widely used library when it comes to managing global states. However, libraries like Zustand try to tackle the problem of managing global states in a more pragmatic and simpler way.

Let’s examine how exactly Zustand differs from Redux.

Simplicity

One drawback to using Redux is the amount of code you have to write to have global state. You have to create reducers, actions, and dispatch functions just to change a very simple state.

The power of Zustand is that creating a global state can be done with four lines of code. That is, if your state is simple, state can save you a lot of time.

Scope

Redux, like Context, needs to be wrapped with a provider component that injects state into all components packaged with provider so that you can use that state in all packaged React components.

With Zustand, this is not necessary. After you create the store, you can inject it wherever you want and once for all components in the project. But that’s probably one of the biggest advantages of state: code that’s not in React.

So you can get data from your state in functions that are called without a React component. For example, using a request function before making a request to the backend.

Developer experience

One of the biggest advantages of libraries like Redux that are popular and have been around for a while is that the developer experience, documentation, and the community are a lot better, so it is easier to find help or answers to your problems.

Even though I would say it is also the case with Redux vs. Zustand, I think the developer experience of Zustand is still positive. The documentation is similar to the library itself, pragmatic, and focused on the things you actually need.

Obviously, it’s not used as widely as Redux, and therefore the community and resources are not as widely spread. However, because the library is pretty simplistic, there aren’t as many questions, problems, or tutorials.

In addition to the community and documentation, one of the first arguments you get when discussing Redux versus another library is that the Redux development tools are powerful.

First, I don’t think you should decide on a library only by its debug tools, but it is a valid argument. But in Zustand, you can use the debug tool as you would in Redux store. Isn’t that amazing?

Redux Debug Tool

Zustand vs. Jotai vs. Recoil

Interestingly, the Jotai library and Zustand are from the same creators. But, the difference lies in the mental modal and how you structure your application.

According to the Jotai docs, “Zustand is basically a single store (you could create multiple stores, but they are separated.) Jotai is primitive atoms and composing them. In this sense, it’s the matter of programming mental model.

“Jotai can be seen as a replacement for useState+useContext. Instead of creating multiple contexts, atoms share one big context. Zustand is an external store and the hook is to connect the external world to the React world.”

The last sentence is, in my opinion, the most important one when it comes to what makes Zustand so different from other state management tools. It was basically built for React but is not tied to it.

This means it can be a tool to connect the React world with the nonReact world. How is this possible? Because the state is not built on top of React’s context API. You probably also noticed that you don’t need to add a root provider somewhere in your application during installation.

What makes Zustand so special?

There are two things that impress me about Zustand: it’s not only for React and it’s 100 percent unopinionated.

I’ve mentioned it before, but what makes Zustand a great tool is that it’s not tied to the React context, and thus, not tied to use within a React application or React itself.

For example, you can combine the state of different applications no matter what framework they use (I’m looking at you, micro frontends).

Also, it’s completely unopinionated. While this sounds pretty obvious, in the world of state management in React, I immediately jumped on the Redux ecosystem bandwagon without even thinking about what benefits it could bring.

Zustand is one of the examples (and this is also true for the other libraries like Jotai or Recoil) where simplicity wins over over-engineering.

Disadvantages of using Zustand

Overall, Zustand is a great library for pragmatic programmers and those who use React, but in combination with another library.

However, Zustand has its cons as well. For one, the documentation could be improved. As of the time of writing, the only documentation at the moment is the project’s readme.

While it’s well written so you can easily understand the library, it doesn’t cover all of the use cases.

For instance, if we look at the persist function, you can see two configuration options in the example, but to see all available options, you must open the code and check the implementation directly. Or, if you use TypeScript, you might figure it out by the typings.

Store structure is clunky, as well. When creating a store, it must always be done within the create function, and the edit functions need the set function added to the callback function.

This means you must write your state functions within the scope of the callback function or you must pass the set function to it. This can be clunky when writing more complex manipulation functions.

The current state of state management

In my opinion, the days of how we used Redux originally are numbered. Global state management can be quite tricky and, therefore, should be something that is not made artificially complicated.

I’m not saying Redux isn’t useful, but it can cause you to over-engineer an initially simple state, which is why I was so impressed with the idea that Zustand touts simplicity. Now, we have plenty of options to choose from, so Redux may no longer be the default go-to for all of your state management.

But in the end, it can really vary from project to project and to say that there is one library that solves all of our problems is not realistic, but at least we have more options, and it should not be the default option to choose Redux for your state management in all applications.

: Full visibility into your web and mobile apps

LogRocket is a frontend application monitoring solution that lets you replay problems as if they happened in your own browser. Instead of guessing why errors happen, or asking users for screenshots and log dumps, LogRocket lets you replay the session to quickly understand what went wrong. It works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store.

In addition to logging Redux actions and state, LogRocket records console logs, JavaScript errors, stacktraces, network requests/responses with headers + bodies, browser metadata, and custom logs. It also instruments the DOM to record the HTML and CSS on the page, recreating pixel-perfect videos of even the most complex single-page and mobile apps.

Full visibility into production React apps

Debugging React applications can be difficult, especially when users experience issues that are hard to reproduce. If you’re interested in monitoring and tracking Redux state, automatically surfacing JavaScript errors, and tracking slow network requests and component load time, try LogRocket.

LogRocket is like a DVR for web and mobile apps, recording literally everything that happens on your React app. Instead of guessing why problems happen, you can aggregate and report on what state your application was in when an issue occurred. LogRocket also monitors your app's performance, reporting with metrics like client CPU load, client memory usage, and more.

The LogRocket Redux middleware package adds an extra layer of visibility into your user sessions. LogRocket logs all actions and state from your Redux stores.

Modernize how you debug your React apps — .

.
Lorenz Weiß Hi, I'm Lorenz, a frontend-focused web developer. I'm in love with the internet and its people and interested in everything related to it.

3 Replies to “Managing React state with Zustand”

  1. Look at Hookstate https://hookstate.js.org, you will be very much surprised how simpler it is, more feature rich and incredibly fast, especially for the deep nested state updates, which are the problem for all other state management tools.

  2. Hello, would it be possible to create a blog post about zustand maybe implementing some authentication work flow maybe with next.js and localstorage presistance?

Leave a Reply