React state management with Zustand🐻
Tags: zustand, react
Published: 2023-08-08 01:18:29

With all the state management libraries that we currently have, deciding what to use for your next project can be douting, but if you want efficiency without complexity, then Zustand is probably your choice. 
In this article, we shall discuss some advantages provided by Zustan, and how to introduce Zustand in your React project.

###Why zustand
Zustand is not the only good state management library out there, and we have some of them that have been in the field for so long, like Redux and Mobx, but what makes it special? And why is it probably the next for your next project? Here are 3 reasons why Zustand matters and why you should opt for it!

<strong>Minimalism and simplicity</strong> : Zustand was designed to be simple with a minimal approach, making it easy to learn and use.



<strong>Small size bundle</strong>: <span> with only 1.16kb of bundle size, Zustand is by far the smallest react state management library and yet one of the performers.
</span>


<strong>selective component subscription </strong> : Zustand allow components to only subscribe state they need, which improves performance by avoiding unnecessary re-render 

I think now you are convinced, and can't wait to see what Zunstand has for you. For demonstrating all those amazing features we shall be building one Todo application that will showcase the fundamentals of Zustand

<h4>Project setup</h4>
we shall use vite and tailwind.css for applying some basics styling, you can go with any CSS framework you want or just use plain CSS. Please note that everything will be built with Typescript.

For saving time and focus on Zustand, I created a simple starter project that contains all installed dependencies and a basic todo UI  here is the link to the starter project [repository](https://github.com/DjibrilM/zustand-starter) 

If you downloaded the starter project I'm sure you found an `App.tsx` file and `Todo.tsx` file, in the `App.tsx` file we got an input text that will be used for creating todos and a list element containing our todos.
The `Todo.tsx` contains the code for rendering a todo and two buttons, one for deleting the todo, another one for updating the todo, and the last one that will be used for updating the status of the todo


<h3>Zustand store</h3>

Zustand embraces the idea of actions for updating states, Zustand actions are different from Redux, since actions in Zustand are just properties of the store object. Zustand has several ways of managing stores, but in this article, we shall focus on the easiest way, use the following resource for more [information](https://docs.pmnd.rs/zustand/getting-started/comparison) 


<h3>Todo store</h3>

```ts 
import { create } from "zustand";

export interface Todo {
    id: string,
    completed: boolean,
    content: string,
}


interface state {
    todos: Todo[],
    length: number,
}

interface Actions {
    addTodo: (newTodo: Todo) => void,
    markCompleted: (id: string) => void;
    deleteTodo: (id: string) => void,
    clearAllCompleted: () => void,
    updateTodo: (id: string, content: string) => void
}

const useTodoStore = create<state & Actions>((set, get) => ({
    todos: [],
    length: 0,
    addTodo: (newTodo) => set({ todos: [newTodo, ...get().todos,], length: get().todos.length }),
    markCompleted: (todoId) => {

        const getTodos = get().todos  // get the list of all todos avalaible in the store

        const todoIndex = getTodos.findIndex((todo) => todo.id === todoId);  //find todo index

        getTodos[todoIndex].completed = !getTodos[todoIndex].completed;  //update the complete property by its oposite.

        return set({ todos: [...getTodos], length: get().todos.length });  //update the list all todos in the store with the new changes.

    },

    deleteTodo: (todoId) => set({ todos: get().todos.filter((todo) => todo.id !== todoId) }),  //delete the targeted todo.

    clearAllCompleted: () => set({ todos: [] }),

    updateTodo: (id: string, content: string) => {
        const getTodos = get().todos; //get the list of all todos

        const todoIndex = getTodos.findIndex((td) => td.id === id); //get the targeted todo index

        getTodos[todoIndex].content = content;

        return set({ todos: [...getTodos] });
    }

}))

export default useTodoStore;
```


I know you have one million questions about the above code, but don't worry I have an explanation for you, the three interfaces are types of data available within the store,  the create method used for creating stores provides two methods, a set method for updating states and a get method for getting 
available states in the store, the get method is not the only option for the getting state available in the store here is another way 


```ts 
const  store  =  create((set)  =>  ({
    name:  "may be your name",
    getName:  set((state)  =>  state.name)  //access state from set state
}))
```


<h4>Data consumption</h4>

After creating our lovely store let's consume its state now, we shall start by creating a method for creating todos and then printing  them.

_App.tsx file_
```ts 
import { useRef } from 'react'
import Todo from './components/Todo'
import useTodoStore from './store/features/todo'
import { Todo as TodoType } from './store/features/todo';


function App() {
  const addTodoAction = useTodoStore((state) => state.addTodo);
  const todos = useTodoStore((state) => state.todos);
  const input = useRef<HTMLInputElement>();

  const addTodo = () => {
    if (!input.current?.value) return;

    const newTodo: TodoType = {
      content: input.current.value,
      completed: false,
      id: new Date().toISOString(),
    }
    addTodoAction(newTodo);
    input.current.value = "";
  }


  return (
    <main className=' w-full p-2 min-h-screen bg-slate-200 pt-10'>
      <div className=" bg-white border m-auto  max-w-4xl min-h-[300px] ">
        <div className="">
          <h3 className=' text-2xl font-bold p-5 border-b bg-slate-100 text-slate-600'>Simple Todo📝</h3>
        </div>

        <div className="w-full flex   gap-5 p-5">
          <input ref={(el: HTMLInputElement) => input.current = el} placeholder=' Your todo' type="text" className='  px-4 w-full rounded-md border outline-slate-300' />
          <button onClick={() => addTodo()} className=' bg-slate-700 text-white px-10 py-4 active:bg-slate-800 rounded-md '>submit</button>
        </div>

        <ul className=' p-5'>
          {todos.map((td) => <Todo content={td.content} completed={td.completed} id={td.id} key={td.id} />)}
        </ul>
      </div>
    </main>
  )
}

export default App
```

If you paid attention to details, you probably notice that I did not use any extra hook or a selector for using the store, that is because Zustand is a direct state management library, with Zustand you don't need any extra hook for accessing the store's state, and the store can be accessed outside of the react tree.

<h3>Finish the todo app</h3>
In this last section of the article, we shall create all the remaining functions, like the one for deleting, updating, and marking todos as completed or not.

_Todo.tsx file_ 

```ts 
import { BsTrash3 } from 'react-icons/bs';
import { MdModeEditOutline } from 'react-icons/md';
import React, { FormEvent, useState, } from 'react';
import useTodoStore from '../store/features/todo';


interface Props {
    content: string,
    completed: boolean,
    id: string
}

const Todo: React.FC<Props> = ({ content, completed, id }) => {
    const [editMode, setEditMode] = useState<boolean>(false);
    const [updateValue, setUpdateValue,] = useState<string>(content);


    //actions from useTodoStore
    const { deleteTodo, updateTodo, markCompleted } = useTodoStore((state) => state);


    //function for updating a todo
    const editTodFunction = (e: FormEvent) => {
        e.preventDefault();
        if (updateValue.trim().length < 1) return //check if the input is empty

        updateTodo(id, updateValue);
        setEditMode(false);
    }


    return (
        <li className=' w-full my-2 cursor-pointer  border flex gap-3 items-center bg-slate-50 p-5 rounded-md'>
            {
                editMode ?
                    <div className="w-full border ">
                        <form onSubmit={editTodFunction} action="">
                            <input autoFocus onChange={(e) => setUpdateValue(e.target.value)} type="text" defaultValue={content} className=' bg-transparent text-slate-500 h-10 w-full outline-none p-2' />
                        </form>
                    </div>

                    : <div className=" w-full">
                        <p style={completed ? { textDecoration: "line-through" } : {}} className=' text-slate-600'> {content} </p>
                    </div>
            }


            <div className=" flex gap-7">
                <button onClick={() => deleteTodo(id)} className=' p-3  rounded-full box-border w-12 h-12 flex items-center justify-center bg-slate-200 hover:bg-slate-300 hover:text-slate-700'>
                    <BsTrash3 className=" cursor-pointer" />
                </button>

                <button onClick={() => setEditMode(!editMode)} className=' p-3  rounded-full box-border w-12 h-12 flex items-center justify-center bg-slate-200 hover:bg-slate-300 hover:text-slate-700'>
                    <MdModeEditOutline className=" text-slate-500" />
                </button>

                <button onClick={() => markCompleted(id)} className=' p-3  rounded-md box-border bg-slate-200 text-sm hover:bg-slate-300 hover:text-slate-700'>
                    Completed
                </button>
            </div>
        </li>
    )
}


export default Todo

```

#### Bonus(persisting data)

What is a todo application without persisting data 🥱 Zustand allows stores to persist data not just in the local storage but with any other storage mechanism that the browser provides, in the example below we transform our todo store into a store that persists its data in the local storage, you can be very creative and use other storage like the indexDB, here is a link to the complete [resource 
](https://docs.pmnd.rs/zustand/integrations/persisting-store-data)


```ts 

const useTodoStore = create(persist<state & Actions>(
    (set, get) => ({
        todos: [],
        length: 0,
        addTodo: (newTodo) => set({ todos: [newTodo, ...get().todos,], length: get().todos.length }),
        markCompleted: (todoId) => {

            const getTodos = get().todos  // get the list of all todos avalaible in the store

            const todoIndex = getTodos.findIndex((todo) => todo.id === todoId);  //find todo index

            getTodos[todoIndex].completed = !getTodos[todoIndex].completed;  //update the complete property by its oposite.

            return set({ todos: [...getTodos], length: get().todos.length });  //update the list all todos in the store with the new changes.

        },

        deleteTodo: (todoId) => set({ todos: get().todos.filter((todo) => todo.id !== todoId) }),  //delete the targeted todo.

        clearAllCompleted: () => set({ todos: [] }),

        updateTodo: (id: string, content: string) => {
            const getTodos = get().todos; //get the list of all todos

            const todoIndex = getTodos.findIndex((td) => td.id === id); //get the targeted todo index

            getTodos[todoIndex].content = content;

            return set({ todos: [...getTodos] });
        }

    }),
    {
        name: 'food-storage', // name of the item in the storage (must be unique)
        storage: createJSONStorage(() => localStorage), // (optional) by default, 'localStorage' is used
    }
))

export default useTodoStore;

```

And that is it. That was a little introduction to Zustand, Zustand is not only about what I have shown you in this article, you can go beyond that, you can maybe learn  how to fetch data correctly... there is always room for growth 😇

Thanks for reading 🙏, if you want some cool stuff just leave a comment, don't forget to like and follow for more contents like this.

Link to the full [code](https://github.com/DjibrilM/zustand-guide)