DEV Community

Cover image for State Management on React [Part 5] - Zustand
Kevin Toshihiro Uehara
Kevin Toshihiro Uehara

Posted on • Updated on

State Management on React [Part 5] - Zustand

What's up Folks!!

It's nice to have you here again!!!
This is the fifth and last part of articles of react management. How are you feeling with so much technologies? Amazing, isn’t it?

SpongeBob Tired Image

I hope you guys liked the previous article where I talk about state management using the Recoil. If you did not saw the previous part you can find here: Recoil State Management

We are going to create a simple application but we will be able to see the code of the technologies and compare the implementation.

Just to remember, let's get situated:

Summary:

Introduction

Zustand is comparable to Redux, as Jotai with Recoil.

According to the documentation of zustand:

A small, fast and scalable bearbones state-management solution using simplified flux principles. Has a comfy API based on hooks, isn't boilerplatey or opinionated.

Don't disregard it because it's cute. It has quite the claws, lots of time was spent dealing with common pitfalls, like the dreaded zombie child problem, react concurrency, and context loss between mixed renderers. It may be the one state-manager in the React space that gets all of these right.

Zustand Image of main page

Just to remember again (sorry but it's important), if you did not saw the first part, I created all components that we will use in this and next parts on the first part. Soooo if you did not saw yet, I recommend to see on: State Management on React [Part 1] - Context API.

We will use the first version of the components that we created without the Context API integration.

Now with all concepts at hands, LET'S TO THE CODE!

Show me the code

Let's add the dependencies that we will use:

yarn add zustand
Enter fullscreen mode Exit fullscreen mode

easy peasy!

And now, we don't need to a provider on our main.txt.

So I will create a directory on /src called store and for each feature I will create a folder with the name of the feature. So, we have two features in our app: change theme color (dark mode) and a todo list:

Image of directory’s

Let's start with the dark mode feature. We will create a file on store/theme/index.ts and create this code:

import { create } from "zustand";

export interface ITheme {
  isDark: boolean;
  changeTheme: VoidFunction;
}

export const useTheme = create<ITheme>((set) => ({
  isDark: false,
  changeTheme: () => {
    set((state: ITheme) => ({ isDark: !state.isDark }));
  },
}));
Enter fullscreen mode Exit fullscreen mode

I'm using an interface to define our typess on our zustand hooks to manage the state. Zustand offers the create function to we manage the state.

Now let's see how the todo store will it be.

Let's create on store/theme two files. First the types types.ts (my preference) and the index.ts.

The types.ts contains just the type of our todo:

store/todo/types.ts

export interface ITodo {
  id: number;
  text: string;
  done: boolean;
}

export interface ITodoState {
  todos: ITodo[];
  addTodo: (text: string) => void;
  removeTodo: (id: number) => void;
}
Enter fullscreen mode Exit fullscreen mode

And creating the store for our todo list on index.ts and the code will be:

store/todo/index.ts

import { create } from "zustand";
import { ITodo, ITodoState } from "./types";

export const useTodo = create<ITodoState>((set) => ({
  todos: [],
  addTodo: (text: string) => {
    const newTodo: ITodo = {
      id: Math.random() * 1000,
      text,
      done: false,
    };

    set((state: ITodoState) => ({
      todos: [...state.todos, newTodo],
    }));
  },
  removeTodo: (id: number) => {
    set((state: ITodoState) => ({
      todos: state.todos.filter((todo) => todo.id !== id),
    }));
  },
}));
Enter fullscreen mode Exit fullscreen mode

The create function pass the set parameter for we update the state. Now we can see the similarities with redux, when I introduce the slicers.

Now we will change the components to use our zustand state.

First, let's change the ButtonChangeTheme:

components/ButtonChangeTheme/index.tsx

import { ITheme, useTheme } from "../../store/theme";
import button from "./button.module.css";

interface ButtonChangeThemeProps {
  label: string;
}

export const ButtonChangeTheme = ({ label }: ButtonChangeThemeProps) => {
  const changeColor = useTheme((state: ITheme) => state.changeTheme);

  return (
    <button className={button.btn} onClick={changeColor}>
      {label}
    </button>
  );
};
Enter fullscreen mode Exit fullscreen mode

We just need to use the hook that we created and get the function that we created.

Now changing the Content dummy component:

components/Content/index.tsx

import { ITheme, useTheme } from "../../store/theme";

interface ContentProps {
  text: string;
}

export const Content = ({ text }: ContentProps) => {
  const isDark = useTheme((state: ITheme) => state.isDark);

  return (
    <div
      style={{
        height: "30vh",
        width: "100vw",
        color: isDark ? "#fff" : "#111827",
        backgroundColor: isDark ? "#111827" : "#fff",
      }}
    >
      {text}
    </div>
  );
};
Enter fullscreen mode Exit fullscreen mode

The same thing will happens on FormTodo and ListTodo:

components/FormTodo/index.tsx

import style from "./Form.module.css";
import { Button } from "../Button";
import { Input } from "../Input";
import { useState } from "react";
import { useTodo } from "../../store/todo";
import { ITodoState } from "../../store/todo/types";

export const FormTodo = () => {
  const [todo, setTodo] = useState("");
  const addTodo = useTodo((state: ITodoState) => state.addTodo);

  const handleAddTodo = () => {
    addTodo(todo);
    setTodo("");
  };

  return (
    <div className={style.formContainer}>
      <Input
        value={todo}
        label="Todo"
        onChange={(evt) => setTodo(evt.target.value)}
      />
      <Button label="Adicionar" onClick={handleAddTodo} />
    </div>
  );
};
Enter fullscreen mode Exit fullscreen mode

components/ListTodo/index.tsx

import { useTodo } from "../../store/todo";
import { ITodoState } from "../../store/todo/types";
import style from "./ListTodo.module.css";

export const ListTodo = () => {
  const removeTodo = useTodo((state: ITodoState) => state.removeTodo);
  const todos = useTodo((state: ITodoState) => state.todos);

  return (
    <ul>
      {todos.map((todo) => (
        <li className={style.item} key={todo.id}>
          <label>{todo.text}</label>
          <i className={style.removeIcon} onClick={() => removeTodo(todo.id)} />
        </li>
      ))}
    </ul>
  );
};
Enter fullscreen mode Exit fullscreen mode

And for the fourth time we finish the same app!

Results

Zustand has many similarities with Redux.

But what I introduced to you it's just the basic of Zustand. Read the documentation to see more details.

The visual will not change but let's see 👀 using the React Dev Tools with highlighting render:

Zustand highlight gif dev tolls

And the React Dev Tools Profiler:

Zustand Profiler git dev tools

Conclusion

Zustand is offers a minimal API to manager our state. It simple and easy to maintain our state.

But different of Redux, zustand offers:

  • Makes hooks the primary means of consuming state
  • Doesn't wrap your app in context providers
  • Less boilerplate

I'm not saying that zustand is the best library and that you should use it in all your project. Or that all the state managers that I introduce to you are the best.

Like everything in computing the answer is: it depends.
It depends of your project, product, solution, team, knowledges and a lot of more...

So much so that the purpose of these articles is to compare the application and implementation of each state manager.

So in summary, in this article we saw how we can implement the zustand and manipulate our global state.

It was a pleasure to bring this content to you. I hope I have somehow added some knowledge or brought some technology that can help you.

It's been a long journey, but we're done here. See you in some upcoming articles. Thank you very much.

Some references:

That's all folks!

Thank you so much!!!

Image description

Top comments (0)