DEV Community

Cover image for Learn Zustand Right Now in the Simplest Way!
Joodi
Joodi

Posted on

Learn Zustand Right Now in the Simplest Way!

Managing state in React has never been this simple and lightweight! Meet Zustand, a small but powerful state management library that will make your life as a developer much easier. Whether you're tired of Redux's boilerplate or Context API's limitations, Zustand is here to save the day.

In this post, I'll introduce Zustand and share a starter project I've built, available on GitHub. Follow along to get started with Zustand in a Next.js project in just a few steps! 🐻

Image description

What is Zustand? πŸ€”

Zustand (German for "state") is a minimalistic state management library for React. It offers:

  • A super-simple API.
  • High performance with no unnecessary re-renders.
  • Easy-to-understand syntax.
  • Built-in middleware support (e.g., for persisting state).

Now, let’s dive into setting it up in your Next.js project!


How to Set Up Zustand in Your Next.js Project

Follow these easy steps to integrate Zustand into your Next.js app.

1. Install Zustand

Run the following command to install Zustand:

npm i zustand
Enter fullscreen mode Exit fullscreen mode

2. Create a Store Folder

Inside your src folder, create a new folder named store. This will hold all your Zustand state files.

src/
  store/
    index.ts
    userStore.ts
Enter fullscreen mode Exit fullscreen mode

3. Add Your Stores

userStore.ts

This store will handle user-related data.

import { create } from "zustand";

interface User {
  id: string;
  name: string;
  email: string;
}

interface UserStore {
  user: User | null;
  setUser: (user: User | null) => void;
}

export const useUserStore = create<UserStore>((set) => ({
  user: null,
  setUser: (user: User | null) => set({ user }),
}));
Enter fullscreen mode Exit fullscreen mode

counterStore.ts (with persist)

This store will handle a counter state and persist it in localStorage.

"use client";

import { create } from "zustand";
import { persist, createJSONStorage } from "zustand/middleware";

interface ICounterStore {
  counter: number;
  increment : ()=> void;
  decrement : ()=> void;
  reset : ()=> void;
  getLatestCountDivided2: ()=> number;
}
export const useCounterStore = create<ICounterStore>()(
  persist(
    (set, get) => ({
      counter: 0,

      increment: () => set(state => ({ counter: state.counter + 1 })),
      decrement: () => set(state => ({ counter: state.counter - 1 })),
      reset: () => set({ counter: 0 }),
      getLatestCountDivided2: ()=> get().counter / 2,
    }),
    {
      name: "counter-store",
      storage: createJSONStorage(()=> localStorage),
    }
  )
);
Enter fullscreen mode Exit fullscreen mode

index.ts

This file will export all the stores for easier imports.

import { useUserStore } from "./userStore";
import { useCounterStore } from "./counterStore";

export { useUserStore, useCounterStore };
Enter fullscreen mode Exit fullscreen mode

4. Using Zustand in Components

Here’s an example of how to use Zustand stores in your Next.js components.

Home.tsx

"use client";
import { useCounterStore, useUserStore } from "@/store";
import Link from "next/link";

export default function Home() {
  // Access Zustand stores
  const userStore = useUserStore();
  const counterStore = useCounterStore();

  const handleAddUser = () => {
    userStore.setUser({ id: "1", name: "Joodi", email: "mail@example.com" });
  };

  return (
    <div className="">
      <div className="flex gap-2 p-2">
        <Link className="p-2 border" href="/">Home</Link>
        <Link className="p-2 border" href="/about">About</Link>
        <Link className="p-2 border" href="/contact">Contact</Link>
      </div>
      <h1>Hello</h1>
      <h1>User: {userStore.user?.email}</h1>
      <button
        className="bg-blue-500 rounded-md p-2 text-white"
        onClick={handleAddUser}
      >
        Add User
      </button>

      <br />
      <h1>Counter: {counterStore.counter}</h1>
      <button className="bg-blue-500 rounded-md text-white p-2" onClick={counterStore.increment}>
        Increment
      </button>
      <button className="bg-blue-500 rounded-md text-white p-2" onClick={counterStore.decrement}>
        Decrement
      </button>
      <button className="bg-blue-500 rounded-md text-white p-2" onClick={counterStore.reset}>
        Reset
      </button>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Starter Project Repository 🌟

The full code for this starter project is available on GitHub. It’s beginner-friendly and includes everything you need to start using Zustand with Next.js.

Clone the repository and get started:

git clone https://github.com/MiladJoodi/Zustand_Starter_Project.git
cd Zustand_Starter_Project
npm install
npm run dev
Enter fullscreen mode Exit fullscreen mode

Start managing your state effortlessly with Zustand. Let me know what you think, or share your own use cases! 🐻

Top comments (2)

Collapse
 
_arpy profile image
Arpy Vanyan

Hey! Thanks for the tips. Have you explored the specifics of using Zustland in a full-featured Next.js app? What I mean is that Next has its own complexity of SSR and routing, which also needs to be addressed properly. In Zustland's docs they have mentioned the following recommendations:

  • No global stores - Because the store should not be shared across requests, it should not be defined as a global variable. Instead, the store should be created per request.
  • React Server Components should not read from or write to the store - RSCs cannot use hooks or context. They aren't meant to be stateful. Having an RSC read from or write values to a global store violates the architecture of Next.js.

They also use Contex to keep a reference to the store.
What's your take on and experience with that?

Collapse
 
joodi profile image
Joodi • Edited

Thanks for your comment! I agree with the recommendations from the Zustland docs. In a Next.js app, it's crucial to avoid global stores because of SSR, as each request should have its own isolated store to maintain correct state management. React Server Components (RSCs) should indeed remain stateless, and writing to or reading from a store would break the architecture. Using context to reference the store is a solid approach to ensure the store's integrity while maintaining a clean separation of concerns.