Managing State in React with Zustand: A Simplified Approach

Published on | Reading time: 6 min | Author: Andrés Reyes Galgani

Managing State in React with Zustand: A Simplified Approach
Photo courtesy of Adi Goldstein

Table of Contents

  1. Introduction
  2. Problem Explanation
  3. Solution with Code Snippet
  4. Practical Application
  5. Potential Drawbacks and Considerations
  6. Conclusion
  7. Final Thoughts
  8. Further Reading

Introduction

As developers, we often find ourselves wrestling with the complexities of managing state in our applications. Whether you're building a dynamic single-page application (SPA) using React or Vue.js, or simply dealing with form inputs on a basic website, the state can become a juggling act. With libraries and frameworks designed to help, one would expect state management to be smooth sailing. Yet, the reality is that we frequently have to implement fairly complex solutions, trade-offs, or boilerplate code. Enter Zustand, a lesser-known state management tool that not only simplifies shared state management in React applications but also brings some fresh ideas to the table.

Zustand, which means "state" in German, is a minimalistic state management library created by the minds behind React Spring. Unlike its heavyweight counterparts like Redux or MobX, Zustand takes a different approach—one that is not only intuitive but dramatically reduces boilerplate code, making state management a breeze. But what sets it apart from more established platforms? How can you leverage it to enhance your development workflow?

In this post, we’ll delve into the unique features of Zustand, explore its APIs, and present a simple comparison with React’s context API. By the end, you’ll be well-equipped to make informed decisions about how to manage state in your next project.


Problem Explanation

Most developers have experienced the headache that accompanies state management. Particularly in larger applications, your state can easily spiral out of control. The common approach with libraries like Redux involves a plethora of actions, reducers, and middleware that can lead to substantial boilerplate.

Take a look at the conventional Redux setup:

// actions.js
export const INCREMENT = 'INCREMENT';
export const increment = () => ({ type: INCREMENT });

// reducer.js
const initialState = { count: 0 };
export const countReducer = (state = initialState, action) => {
  switch (action.type) {
    case INCREMENT:
      return { count: state.count + 1 };
    default:
      return state;
  }
};

// store.js
import { createStore } from 'redux';
import { countReducer } from './reducer';
const store = createStore(countReducer);
export default store;

// Component.js
import React from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { increment } from './actions';

const Component = () => {
  const count = useSelector((state) => state.count);
  const dispatch = useDispatch();
  return (
    <>
      <h1>{count}</h1>
      <button onClick={() => dispatch(increment())}>Increment</button>
    </>
  );
};

As seen above, managing a simple count essentially morphs into a convoluted affair with three separate files, multiple imports, and an elaborate flow of actions and reducers. This can lead new developers down a rabbit hole of confusion, increasing both the complexity and potential for bugs.


Solution with Code Snippet

Zustand counteracts the complexity of state management by utilizing a more direct and simplified approach. Instead of Redux's action/reducer structure, Zustand allows you to create a store directly, often reducing your code to a single file or component.

Here’s how you can implement a simple counter with Zustand:

// store.js
import create from 'zustand';

const useStore = create((set) => ({
  count: 0,
  increment: () => set((state) => ({ count: state.count + 1 })),
}));

// Component.js
import React from 'react';
import { useStore } from './store';

const Counter = () => {
  const count = useStore((state) => state.count);
  const increment = useStore((state) => state.increment);

  return (
    <>
      <h1>{count}</h1>
      <button onClick={increment}>Increment</button>
    </>
  );
};

In the Zustand example above, we achieve the same functionality as the Redux example with a fraction of the code and complexity. Zustand’s create function encapsulates both the state and the methods to modify it, allowing you to retrieve state with a simple hook. Not only does this reduce the boilerplate dramatically, but it also keeps the related logic together, enhancing readability and modularity.

Key Benefits Over Redux:

  1. Simplicity: Easy to set up and read.
  2. No Boilerplate: No need for repeated actions or reducers.
  3. Direct State Access: Access and manipulation are streamlined.

Practical Application

Zustand shines when dealing with applications requiring minimal state or even larger apps where you want to avoid the complexity of Redux. For instance, consider a typical e-commerce site with a shopping cart:

// store.js
const useCartStore = create((set) => ({
  items: [],
  addItem: (item) => set((state) => ({ items: [...state.items, item] })),
  removeItem: (id) => set((state) => ({ items: state.items.filter(item => item.id !== id) })),
}));

// Cart.js
const Cart = () => {
  const items = useCartStore((state) => state.items);
  const addItem = useCartStore((state) => state.addItem);
  
  return (
    <>
      {items.map(item => <Item key={item.id} {...item} />)}
      <button onClick={() => addItem({ id: newId, name: 'New Item' })}>Add Item</button>
    </>
  );
};

This pattern cleanly encapsulates cart operations using Zustand without creating a bulk of code, all while keeping functionality intuitive and approachable.


Potential Drawbacks and Considerations

While Zustand is a robust alternative, it isn't without its limitations. One potential drawback is that Zustand works only with React. If you're building a cross-framework application, you might have to consider other options.

Also, while Zustand promotes simplicity, it might not support large-scale applications with intricate state dependencies as effectively as Redux or MobX. In such cases, you might want to opt for more structured libraries if you require features like middleware or time-travel debugging.

Mitigation Strategies

To address the first consideration, ensure that your choice of state management aligns with your technology stack before diving in. To handle the second, consider structuring your state effectively and maximizing Zustand's capabilities by using selectors, thus making your state sliceable and reusable.


Conclusion

In a world where complexity and efficacy are often at odds in state management, Zustand emerges as a breath of fresh air. It allows developers to focus on building features rather than wrestling with convoluted state management systems. Its API feels natural and approachable, promoting cleaner, more maintainable code. By focusing on what truly matters—your application's state—you can save valuable time and resources.

If you're looking for a way to simplify your React application’s state management without losing power or functionality, give Zustand a try. It might just become the trusted sidekick you've been seeking.


Final Thoughts

Have you tried using Zustand in your projects yet? If not, now's the time to jump in and explore! Share your thoughts or any alternative approaches you've encountered in the comments below. And don’t forget to subscribe for more insights into simplifying your development process!


Further Reading


Focus Keyword: Zustand state management
Related Keywords: React state management, minimalistic state management, Zustand vs Redux, JavaScript state management, Zustand store example