DEV Community

Cover image for Zustand: Stupid Simple State Management Tool!
m0nm
m0nm

Posted on

Zustand: Stupid Simple State Management Tool!

Did you ever wanted to have a simple, easy, efficient and lovely state-management solution for your react projects ? then search no more because today i'll show you a pretty neat technology that includes all those attributes. Are you exited ? Let's get started!

What is Zustand

from the creator of Zustand pmndrs:

Zustand is 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.

Check out the repo to learn more

You will see from the example below that Zustand is indeed a small, fast and scalable bearbones state-management solution.

Why Zustand over Redux/Context-API ?

  • Zustand is simple and not opinionated
  • Doesn't wrap your app in a provider
  • Utilizes hooks for handling state
  • No configuration needed

Basic usage

We will create a counter app as an example

  • Install the dependency npm i zustand
  • Create a store
// store/store.js

import create from 'zustand'

export const useStore = create(set => ({
 count: 0,
 incrementCount: () => set(state => ({count: state.count + 1})),
 incrementCount: () => set(state => ({count: state.count - 1})) 
}))
Enter fullscreen mode Exit fullscreen mode

Note
create is a special function that creates a state object with the props we defined
set is a special function that merges the state.

The curly braces inside parenthesis () => ({}) tells javascript that we are returning an object instead of it being a code block

  • Use the hook inside your component
import {useStore} from "./store/store.js"
function MyComponent() {
// you can destrcture the state
// const count = useStore({count} => count)
const count = useStore(state => state.count)
const incrementCount = useStore(state => state.decrementCount)
const incrementCount = useStore(state => state.decrementCount)

return 
    <div>
        <p>Count: {count}</p>
        <button onClick={() => incrementCount}>Increment</button>
        <button onClick={() => incrementCount}>Decrement</button>
    </div>
}
Enter fullscreen mode Exit fullscreen mode

And voilà that's it! simple right ?

Zustand Todo App

We will create the ole Todo app to demonstrate how it easy to work with Zustand

The purpose of this article is just to show how to use Zustand so i'll keep the app simple and not include check, rename or delete functionality for the todos

  • Open the terminal, Create a react app and navigate to it npx create-react-app zustand-example && cd zustand-example

After the installation has been finished we will create a simple form with an input and submit button, So go ahead and type:

import styles from "./App.module.css";
import {useState} from "react"

function App() {
  const handleSubmit = (e) => {
    e.preventDefault()
    }
  return (
    <div className={styles.App}>
      <form onSubmit={handleSubmit} className={styles.form}>
        <input value={value} onChange={(e) => setValue(e.currentTarget.value)} className={styles.input} placeholder="Add a new todo" />
        <button className={styles.button}>Add</button>
      </form>
    </div>
  );
}

export default App;

Enter fullscreen mode Exit fullscreen mode

As you can see this is a generic form with a controlled input, Here is how our form looks like:
!
initial form

This is the styles if you're wondering

.App {
  width: 100%;
  height: 100%;
  display: flex;
  justify-content: center;
  align-items: center;
  background: whitesmoke;
}

.form {
  width: 30%;
  height: 80%;
  background: white;
  border-radius: 12px;
  padding: 2rem;
  display: flex;
  flex-direction: column;
  align-items: center;
  box-shadow: 0 0 5px black;
}

.input {
  width: 80%;
  padding: 10px 12px;
}

.button {
  color: white;
  background: aqua;
  padding: 10px 20px;
  margin: 20px 0;
  border: none;
  width: 140px;
}

Enter fullscreen mode Exit fullscreen mode

We will now implement Zustand

  • first install the dependency npm i zustand
  • inside the src folder create a folder store with store.js in it

store folder

Inside the store object we will create todos property which is an array and addTodo method to push a new todo to the array

import create from "zustand";

export const useStore = create((set) => ({
  todos: [],
  addTodo: (todo) =>
    set((state) => ({
      todos: [...state.todos, todo],
    })),
}));
Enter fullscreen mode Exit fullscreen mode

We're pretty much done here, Now we need to add logic to our form

  • Import useStore hook and call it
import {useStore} from "./store/store"

function App() {
  const todos = useStore((state) => state.todos);
  const addTodo = useStore((state) => state.addTodo);
}
Enter fullscreen mode Exit fullscreen mode
  • Inside handleSubmit function we will submit a new todo to our todos array
const handleSubmit = (e) => {
    e.preventDefault()
    addTodo(value);
}
Enter fullscreen mode Exit fullscreen mode

And finally we will map the todos array to represent the todos

return (
{todos.map((todo) => {
  return (
    <ul>
     <li>{todo}</li>
    </ul>
  );
 })}
)
Enter fullscreen mode Exit fullscreen mode

And that's it!, Lets test our app

final form

Full code:

import { useState } from "react";
import styles from "./App.module.css";
import { useStore } from "./store/store";
function App() {
  const todos = useStore((state) => state.todos);
  const addTodo = useStore((state) => state.addTodo);

  const [value, setValue] = useState("");

  const handleSubmit = (e) => {
    e.preventDefault();
    addTodo(value);
    setValue("");
  };
  return (
    <div className={styles.App}>
      <form onSubmit={handleSubmit} className={styles.form}>
        <input
          value={value}
          onChange={(e) => setValue(e.currentTarget.value)}
          className={styles.input}
          placeholder="Add a new todo"
        />
        <button className={styles.button}>Add</button>

        {todos.map((todo) => {
          return (
            <ul>
              <li>{todo}</li>
            </ul>
          );
        })}
      </form>
    </div>
  );
}

export default App;

Enter fullscreen mode Exit fullscreen mode

Typescript

If you're using typescript you can define the store types with interface

import create from "zustand";

interface IStore {
    todos: string[];
    addTodo: (todo: string) => string
}

export const useStore = create<IStore>((set) => ({
  todos: [],
  addTodo: (todo) =>
    set((state) => ({
      todos: [...state.todos, todo],
    })),
}));

Enter fullscreen mode Exit fullscreen mode

Conclusion

We've reached the end of this post hopefully it encourages you to use Zustand :). If you like this post you can follow me for more, Thanks for reading, Happy state-managing!

Also chack out my other post: "useReducer vs useState"

Some tips on how to build a project

Top comments (2)

Collapse
 
m0nm profile image
m0nm

Curious to know: have you ever used Zustand with a complex project ? How did it go ? Are there other state-management tools do you prefer to use over Zustand ?

Collapse
 
barisx profile image
Barış Şenyerli • Edited

Please fix your code

// store/store.js

import create from 'zustand'

export const useStore = create(set => ({
 count: 0,
 incrementCount: () => set(state => ({count: state.count + 1})),
 decrementCount: () => set(state => ({count: state.count - 1})) 
}))
Enter fullscreen mode Exit fullscreen mode
import {useStore} from "./store/store.js"
function MyComponent() {
// you can destrcture the state
// const count = useStore({count} => count)
const count = useStore(state => state.count)
const incrementCount = useStore(state => state.incrementCount)
const decrementCount = useStore(state => state.decrementCount)

return 
    <div>
        <p>Count: {count}</p>
        <button onClick={() => incrementCount}>Increment</button>
        <button onClick={() => decrementCount}>Decrement</button>
    </div>
}
Enter fullscreen mode Exit fullscreen mode