React-Redux boilerplate makes you mad? Try Zustand!

Nicolás Failla
5 min readMay 7, 2022

--

Photo by mana5280 on Unsplash

Zustand is a lightweight state management solution, with close-to-zero boilerplate. In this post I’m going to show you how to get started with it.

First and foremost, let’s setup our React app, and install Zustand right away:

npx create-react-app my-zustand-app
cd my-zustand-app
npm i zustand

Creating our form

Nice! Let’s start by writing a form for a simple planner app:

src/App.js

Looking great, huh? Not quite. For the sake of simplicity, I’m not giving so much effort to the beauty of this app, but you can replace the css in src/App.css so it looks just a little bit less ugly:

src/App.css
Our nice-looking app

Now, we want to control the form so we can store its values in our state. We will just start out by modeling our state in Zustand. Let’s see how.

Creating the Zustand state

Here comes the magic. Creating a Zustand store is as simple as this:

src/store.js

Now, we must fill in some state properties, and the actions responsible for updating those properties:

src/store.js

The create function returns a hook so we can access our state & its actions:

src/store.js

And that’s all for the Zustand part!

Using it!

Now, let’s connect to the store:

src/App.js

And add some code for rendering the plans array:

src/App.js

Hooray!

get & set functions

get & set are the way-to-go when reading/writing our state. The former returns a reference to the state, actions included, and the latter allows us to update our state in two different ways:

get & set usage

The second version of set is specially useful when your new state depends on the current state, just like in the given example.

Adding Devtools support

Zustand is also compatible with Redux devtools. We just have to add a middleware:

src/store.js

(Note: you also have to have the Redux devtools extension installed, which can be found in your browser’s extensions store. It’s free!)

And that’s it!

Adding Immer

Another great tool for improving our experience is Immer. It lets us update our state using a mutable syntax, by wrapping our state in a JS Proxy object under the hood.

Let me give you an example. Remember this line?

src/store.js

This is fairly OK, but it can quickly get messy, specially when having lots of nested state. Wouldn’t it be cool if we could rewrite it like this?

src/store.js

We just need to add the Immer middleware in order to do so:

src/store.js

And that’s it! Enjoy!

(Optional) Cleanup

With our app in place, we could do some cleanup. Let’s start with the store:

src/store.js

And then the App.js:

src/App.js

I’ve extracted the inputs somewhere else. Check out the final version at the very end of this post.

And I think that’s good enough, although, as always, there are a lot of improvements we could do even in this simple codebase.

Bonus track #1 — debugging

If you’ve headed to the devtools, you might have noticed that the actions are being logged as “anonymous”:

Anonymous actions

You can give the actions a name by providing a third parameter to the set method:

src/store.js

(Note: the second parameter indicates whether we want to completely override our state, actions included, with the new object we provide, or just merge it with the old state. It’s possible to avoid passing this argument if merging is the default behavior we want, but that’s a subject for another post.)

Now we are talking:

Named actions

On the other hand, you might want to console.log some piece of state. If you‘ve set up Immer and try to do so, you’ll stump your head with a nice-looking Proxy:

Immer-powered state being logged in the console right away

The easiest way to overcome that problem is to serialize+deserialize the proxy before logging it:

Simplest way to serialize+deserialize an object in JS

(Edit: Immer comes with an original function which returns the unproxied object. It works way faster, and is the recommended way for unproxying an Immer proxied object. More info in the docs.)

Now you can inspect the state:

Immer-powered state being logged in the console after serializing+deserializing it

Bonus track #2 — persisting our state in localStorage

Yet another middleware can be added in order to persist our state in localStorage. This one needs a second parameter, which is an options object. The only one that’s required is name, which will be used to scope the state we are persisting in our localStorage:

app/store.js

This won’t just save our state, but also load it automatically. Try filling the form and refreshing the page!

Our state saved in localStorage

--

--