State manager

Starting with Zustand state management library.

Starting with Zustand state management library.

In today's world, web development has become increasingly complex, and state management is one of the most challenging aspects of web application development. In this article, we will introduce you to a powerful state management library for React called Zustand, which simplifies state management in your React applications.

What is Zustand?

Zustand is a state management library for React that is designed to be simple, fast, and lightweight. It is an alternative to Redux, MobX, and other state management solutions that are often overly complicated and difficult to set up. Zustand is built on top of the Context API and provides a simple and intuitive API for managing state in your React components.

Advantages of Zustand

Zustand has several advantages over other state management solutions, including:

  • Simple API: Zustand's API is simple and intuitive, making it easy to learn and use. You can define your state and actions in a single function, making it easy to manage your state and logic in one place.

  • Performance: Zustand is fast and lightweight, with a small bundle size and minimal performance overhead. It uses the Context API and a custom hook to provide efficient and performant state updates.

  • TypeScript support: Zustand has first-class support for TypeScript, making it easy to add type safety to your state and actions.

  • DevTools integration: Zustand integrates with popular dev tools like React DevTools and Redux DevTools, making it easy to debug and inspect your state.

How to use Zustand

To get started, you can install it via npm or yarn:

npm install zustand

or

yarn add zustand

Once installed, you can define your state and actions in a single function:

Create a new Zustand store:

import { create } from 'zustand'

// define the store
const useCounterStore = create(set => ({
  counter: 0,
}));

Above, we created a store that keeps track of the state of a counter. Its initial value is set to zero. Let's call this store useCounterStore. To define the store, we used a create function imported from state, which takes a create callback function.

How to get data from the store

The useCounterStore is also a hook. You can use it anywhere, without the need for providers. Select your state and the consuming component will render again when that state changes.

const count = useCounterStore(state => state.counter);

return (
    <div className="App">
      <h1>{count}</h1>
    </div>
  );

Updating the state

Updating state with Zustand is easy! Call the set function with the new state, and it will merge with the existing state in memory. Let't update our useCounterStore and add some actions:

const useCounterStore = create(set => ({
  counter: 0,
  increment: () => set(state => ({ counter: state.counter + 1 })),
  decrement: () => set(state => ({ counter: state.counter - 1 })),
}));

Now we can use this actions in our component:

const increment = useStore(state => state.increment);
const decrement = useStore(state => state.decrement);

return (
  <div className="App">
      <h1>{count}</h1>
      <button onClick={increment}>Make +1</button>
      <button onClick={decrement}>Make -1</button>
  </div>
)

Using async requests inside Zustand

import { create } from 'zustand'

export const useCustomer = create(set => ({
  id: 0,
  name: '',
  setCustomer: (id) => {
    set({id});
    fetch(`/endpoint/${id}`).then((id) => id.json()).then(set)
  }
}));

Using a custom Storage engine

If the storage you want to use does not match the expected API, you can create your own storage, using IndexedDB for example:

import { create } from 'zustand'
import { persist, createJSONStorage, StateStorage } from 'zustand/middleware'
import { get, set, del } from 'idb-keyval' // can use anything: IndexedDB, Ionic Storage, etc.

// Custom storage object
const storage: StateStorage = {
  getItem: async (name: string): Promise<string | null> => {
    console.log(name, 'has been retrieved')
    return (await get(name)) || null
  },
  setItem: async (name: string, value: string): Promise<void> => {
    console.log(name, 'with value', value, 'has been saved')
    await set(name, value)
  },
  removeItem: async (name: string): Promise<void> => {
    console.log(name, 'has been deleted')
    await del(name)
  },
}

export const useSomeStore = create(
  persist(
    (set, get) => ({
      apples: 0,
      addApple: () => set({ apples: get().apples + 1 }),
    }),
    {
      name: 'food-storage', // unique name
      storage: createJSONStorage(() => storage),
    }
  )
)

Comparison with other state management libraries

Zustand is a powerful and lightweight state management library that provides an alternative to other popular solutions like Redux and MobX. Here is a comparison of Zustand with these libraries:

  • Redux: Redux is a powerful and widely used state management library for React applications. But it can be complex and difficult to set up, and can add significant boilerplate to your codebase.
    Zustand provides a simpler and more lightweight alternative to Redux, with a smaller learning curve and less overhead.

  • MobX: MobX is another popular state management library for React applications that is known for its simplicity and ease of use. It is often compared to Zustand, as both libraries prioritize simplicity and ease of use. However, Zustand has a smaller bundle apart from being simple and easy to learn. It also offers better performance than MobX.

Use cases for Zustand

Zustand is a versatile state management library that can be used in a variety of applications, including:

  • Small to medium-sized applications: Zustand is a great choice for small to medium-sized applications that require state management but do not have complex requirements. Its simplicity and ease of use make it a great fit for applications that need to get up and running quickly.

  • Real-time applications: Zustand's performance and lightweight design make it a great choice for real-time applications that require fast and efficient state updates. It can handle large amounts of data without impacting performance, making it a great fit for applications that require frequent updates.

  • Applications with complex state requirements: While Zustand is designed to be simple and lightweight, it can also handle complex state requirements. Its API is flexible and can be used to manage any type of state, including objects and arrays.

Using middlewares & Persisting store data

The Persist middleware allows you to store your state in a storage (e.g. localStorage, AsyncStorage, IndexedDB, etc.) and thus persist your data.

Note that this middleware supports both synchronous storages, such as localStorage, and asynchronous storages, such as AsyncStorage, but using an asynchronous storage comes at a cost. You can create an abstract wrapper even for IndexedDB.

Let's create an abstract store to handle current website language:

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

export interface LanguageStoreType {
  currentLocale: string
  updateLanguage: (lang: string) => void
}

export const useLanguageStore = create<LanguageStoreType>()(
  persist(
    (set) => ({
      currentLocale: getLocaleFromUrlParamether(),
      updateLanguage: (lang: string) => {
        set({ currentLocale: lang })
      },
    }),
    {
      name: 'languageStore',
      // (optional) by default, 'localStorage' is used.
      storage: createJSONStorage(() =>
      sessionStorage),
    },
  ),
)

This store has a name prop languageStore which is the key in the LocalStorage. Easy, right?

Now everytime user switches language we will remember his chosen language.

Conclusion

In conclusion, Zustand is a powerful and lightweight state management library for React that offers several advantages over other solutions. Its simple API, performance, TypeScript support, and dev tools integration make it a great choice for small to medium-sized applications, real-time applications, and applications with complex state requirements.

By using Zustand, you can simplify your state management and improve the performance of your React applications.

We already started using it at Muvon, so our current state stack is Zustand + React Query.

A blog for self-taught engineers

Сommunity is filled with like-minded individuals who are passionate about learning and growing as engineers.