Back to blog
Article

Getting Started with Zustand in React and TypeScript: A Powerful State Management Solution

Discover how Zustand revolutionizes state management in React applications with its lightweight and intuitive API, leveraging the power of TypeScript for enhanced development. Simplify your state management workflow and boost performance effortlessly. Read more to unlock the benefits of Zustand.

April 26, 2024Marcus Nguyen
Getting Started with Zustand in React and TypeScript: A Powerful State Management Solution

Introduction:

Zustand is a minimalistic yet powerful state management library for React applications. Built with simplicity and performance in mind, Zustand offers a lightweight alternative to complex state management solutions while leveraging the benefits of modern TypeScript features. In this blog post, we'll explore how to integrate Zustand into a React application using TypeScript and discuss its benefits for state management.

What is Zustand?

Zustand is a state management library inspired by the simplicity of Redux combined with the flexibility of React hooks. It provides a simple API for creating and managing global state in React applications without the need for additional dependencies like Redux or MobX. Zustand uses React's built-in context and hooks to manage state in a concise and efficient manner.

Getting Started:

To get started with Zustand in your React project, you can install it via npm or yarn:

javascript
npm install zustand

To use zustand, we have to import a create function:

javascript
import { create } from "zustand";

This function is called with a callback function and it returns a custom hook. The callback function passed to it is where we will define our state and the functions we can use to manipulate the state. The state and the functions are all in an object returned by this callback function.

Let's see an example:

javascript
const useCounter = create((set) => {
  return {
    counter: 0,
    incrCounter: () => set((state) => ({ counter: state.counter + 1 })),
  };
});

See that the create function, passes a set function to the callback function. This set function is a function used to manipulate the state in the store. States in zustand can be primitives, objects, or functions. In our above example, we have two states in our store: counter, and incrCounter. The useCounter is a custom hook, we can use this hook in our components and we will be able to get the latest state in them. If we use the hook in components A, B, and C. Any change done to the state in B will be reflected in both A and C, and they will all re-render to reflect the new changes.

The custom hook returned by the create acts similarly to useAppSelector in React-Redux, it lets you select a slice of state from the store. You call the hook and pass it a callback function. This function is called by the hook internally and passes the current state to it. So we will then get this state and return the part of the state we want.

Let's see an example.

javascript
const counter = useCounter((state) => state.counter);

See that we called the useCounter hook and passed a callback function to it. Then, we expect a state from the hook and then return the counter part of the state.

We can then, display the counter:

javascript
const DisplayCounter = () => {
  const counter = useCounter((state) => state.counter);
  return <div>Counter: {counter}</div>;
};

Now, we want to create a component where we can increase the value of the counter state.

javascript
const CounterControl = () => {
  const incrCounter = useCounter((state) => state.incrCounter);

  return (
    <div>
      <button onClick={incrCounter}>Incr. Counter</button>
    </div>
  );
};

This is a separate component from where we increase the value of the counter state. See that we sliced out the incrState function from the state, and we set it to the onClick event of the button. This will increase the counter state when the button is clicked. See how the components are independent of each yet they can "see" the current state from the store. Whenever we click the Incr. Counter button in the CounterControl component, the DisplayComponent will re-render to display the newest counter state value.

Let's see how we use them:

javascript
const App = () => {
  return (
    <>
      <DisplayCounter />
      <CounterControl />
    </>
  );
};

They are independent of each other yet magically connected by zustand. This gives React-Redux a run for its money because trying to re-create this small state in Redux-React will take more code to set up:

  • First, we will create a store.
  • We will wrap either the App component or its children in a Content Provider and pass the store to the Context Provider via a store props.
  • We will import useSelector, useDispatch in any component we wish to use in the store.
  • To get a slice of the state we will call the useSelector with a callback function.
  • To dispatch an action to the store, we will use the useDispatch hook.

It's quite lengthy, but with Zustand it's oversimplified.

Returning the whole state Now, when we call the custom hook returned by the create function without a callback function, the hook will return the whole state of the store.

javascript
const state = useCounter();

See that we called the useCounter hook with no callback function, so in this case, the function will return the whole state in the store.

The state holds the whole state in the useCounter store. We can get the counter state by doing this:

javascript
state.counter;
// 0

Benefits of Zustand:

  1. Simplicity: Zustand offers a minimalist API, making it easy to set up and use. With fewer concepts to learn compared to other state management libraries, developers can focus on building features rather than managing complex state logic.
  2. Type Safety with TypeScript: Zustand seamlessly integrates with TypeScript, providing strong type safety and eliminating runtime errors associated with state management. TypeScript's static typing ensures that state properties and actions are correctly typed throughout the application, enhancing code quality and developer productivity.
  3. Performance: Zustand is built with performance in mind. It leverages React's built-in context and hooks under the hood, resulting in efficient state updates and minimal re-renders. This leads to faster rendering times and improved overall application performance.
  4. Scalability: Zustand scales well with large and complex applications. Its flexible architecture allows developers to organize state logic in a way that best suits their application's needs. Whether managing simple local component state or global application state, Zustand provides a scalable solution for any project size.
  5. Minimal Bundle Size: Unlike some other state management libraries, Zustand has a small footprint. It adds minimal overhead to your application bundle, keeping load times low and optimizing performance, especially for applications targeting mobile devices or slower networks.
  6. No Boilerplate: With Zustand, there's no need for boilerplate code commonly associated with traditional state management solutions like Redux. Zustand's concise syntax and intuitive API enable developers to write clean and maintainable code without sacrificing functionality.
  7. React Hooks Integration: Zustand embraces the power of React hooks, allowing developers to leverage familiar concepts like useState and useEffect to manage state. This makes Zustand easy to learn for developers already familiar with React's hooks API.
  8. Developer Experience: Zustand improves developer experience by providing a straightforward and flexible approach to state management. Its minimalistic design and clear documentation enable developers to quickly get up and running with state management in their React applications.