Will's avatar

⬅️ See more posts

React State Management with Zustand

5 February 2021 (5 minute read)

🔮 This post is also available via Gemini.

100daystooffload technology javascript react

💯 100 Days to Offload

This article is one of a series of posts I have written for the 100 Days to Offload challenge. Disclaimer: The challenge focuses on writing frequency rather than quality, and so posts may not always be fully planned out!

View other posts in this series.

React state

React state management is what gives the library its reactiveness. It’s what makes it so easy to build performant data-driven applications that dynamically update based on the underlying data. In this example the app would automatically update the calculation result as the user types in the input boxes:

import React, { useState } from 'react';

function MultiplicationCalculator() {
  const [number1, setNumber1] = useState(0);
  const [number2, setNumber2] = useState(0);
  return ( <>
    <input value={number1} onChange={e => setNumber1(parseInt(e.target.value))} />
    <input value={number2} onChange={e => setNumber2(parseInt(e.target.value))} />
    <p>The result is {number1 * number2}.</p>
  </> );
}

The resultant React app, showing two text inputs and a result line

The entire function will re-run on each state change (the setNumber1 and setNumber2 functions) in order to reactively update the result text. The multiplication itself could be calculated in a useEffect but it is simpler to look at it as shown.

This is totally fine for many apps, however this quickly becomes unmanageable when you need to share state (e.g. number1) between this component and another component - and ensure that a state change in the former can be reflected in the latter - whether it’s an ancestor, descendant, or a more distant component. Of course, you can pass the state variables (and the associated setState functions) from a parent down as props to child components, but as soon as you’re doing this more than a handful of times or in cases where state needs to be shared across distant components this quickly becomes hard to maintain or understand.

An example of shared state might be to store the details about the currently logged-in user in an app. A navigation bar component would need to know about the user state to show a link to the correct profile page, and another component may need access to the same state in order to allow the user to change their name.

Context and Redux

This is by no means a new problem. Many of these issues are solved using React’s Context API and there are also libraries like Redux that are useful in perhaps more complex scenarios - it’s much more opinionated and involves a fair bit of extra code that may be overkill in many apps. Adding just a small piece of state (e.g. a new text input), and the ability to alter it, to Redux involves updating reducers, creating an action, dispatchers, and wiring things through to your components using connect, mapStateToProps, and mapDispatchToProps. Plus you’ll need the relevant provider higher up.

Redux is certainly a fantastic library, however, and I use it in many apps. This post is useful and discusses the cases in which you may (or may not) want to use Redux.

Zustand

In this post I want to talk about another option that is perhaps quicker and easier to use, expecially for those newer to React (though it’s also great for more seasoned React developers) - zustand. Not only is this the German word for “state”, it’s also a nice and succinct library for state management for React.

The zustand library is pretty concise, so you shouldn’t need to add too much extra code. To get started just add it as a dependency to your project (e.g. yarn add zustand). Now let’s rewrite the earlier multiplication example but using zustand.

First, define a store for your app. This will contain all of the values you want to keep in your global state, as well as the functions that allow those values to change (mutators). In our store, we’ll extract out the state for number1 and number2 we used in our component from earlier, and the appropriate update functions (e.g. setNumber1), into the store:

import React from 'react';
import create from 'zustand';

const useStore = create((set) => ({
  number1: 0,
  number2: 0,
  setNumber1: (x) => set(() => ({ number1: x })),
  setNumber2: (x) => set(() => ({ number2: x })),
}));

Now - in the same file - we can go ahead and rewrite our component such that it now uses this store instead of its own local state:

function MultiplicationCalculator() {
  const { number1, number2, setNumber1, setNumber2 } = useStore();
  return ( <>
    <input value={number1} onChange={e => setNumber1(parseInt(e.target.value))} />
    <input value={number2} onChange={e => setNumber2(parseInt(e.target.value))} />
    <p>The result is {number1 * number2}.</p>
  </> );
}

That’s it - we now have a React app that uses zustand. As before, the component function runs each time the store’s state changes, and zustand ensures things are kept up-to-date.

In the example above the two blocks of code are in the same file. However, the power of zustand becomes particularly useful when the store is shared amongst several components across different parts of your app to provide “global state”.

For example, the useStore variable could be declared and exported from a file named store.js somewhere in your app’s file structure. Then, when a component needs to access its variables or mutator functions it just needs to - for example, import useStore from 'path/to/store' - and then use object destructuring (as on line 11 above) to pull out the needed variables and functions.

It’s worth checking out the documentation since zustand is super flexible and can be used in ways that help improve performance, such as taking advantage of memoizing and state slicing. It also makes what can be tricky in other such libraries - e.g. asynchronous state updates - trivial.

If you’ve already got an established app using another state management system it may not be worth migrating everything over. But give zustand a go in your next project if you’re looking for straight forward, yet powerful, state management.

✉️ You can reply to this post via email.

📲 Subscribe to updates

If you would like to read more posts like this, then you can subscribe via RSS.