React-Redux boilerplate makes you mad? Try Zustand!
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:
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:
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:
Now, we must fill in some state properties, and the actions responsible for updating those properties:
The create function returns a hook so we can access our state & its actions:
And that’s all for the Zustand part!
Using it!
Now, let’s connect to the store:
And add some code for rendering the plans array:
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:
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:
(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?
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?
We just need to add the Immer middleware in order to do so:
And that’s it! Enjoy!
(Optional) Cleanup
With our app in place, we could do some cleanup. Let’s start with the store:
And then the 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”:
You can give the actions a name by providing a third parameter to the set method:
(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:
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:
The easiest way to overcome that problem is to serialize+deserialize the proxy before logging it:
(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:
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:
This won’t just save our state, but also load it automatically. Try filling the form and refreshing the page!
Before you go
Don’t forget to get the final version from the repo:
And stay tuned! I hope you enjoyed it!