DEV Community

Cover image for šŸ’»Mastering State Management with Zustand in Next.js and React āš›
MrSuperCraft
MrSuperCraft

Posted on

šŸ’»Mastering State Management with Zustand in Next.js and React āš›

Introduction

State management is one of the cornerstones of React applications. As your app grows in complexity, managing the state efficiently can become a challenge. In large applications, tools like Redux or Context API might seem over-complicated, with a lot of boilerplate code and performance concerns.

In this article, weā€™ll explore Zustand, a minimalistic state management library, and integrate it with the App Router in Next.js (version 13+). Zustand offers a simple and flexible approach to managing global state without the overhead of Redux or Context API, while also being well-suited for modern Next.js applications.

By the end of this article, you'll have a clear understanding of how Zustand works with the App Router and be ready to implement it in your own projects.

What is Zustand?

Zustand is a lightweight state management library that simplifies state handling in React applications. With no reducers or actions, Zustand enables easy state management by directly creating stores. Itā€™s a great fit for applications where you want to avoid the complexity of Redux but still need a global state solution.

Key Benefits of Zustand:

  • Minimal boilerplate: No actions, reducers, or providers needed.
  • Performance-focused: Components only re-render when the specific parts of the state they subscribe to change.
  • Simple API: Easy to integrate with any React application, including Next.js.

Setting Up Zustand with the App Router in Next.js

Setting up Zustand with Next.js using the App Router is very straightforward. The App Router is the default for new Next.js apps, leveraging the new file-system-based routing and support for server-side rendering.

1. Install Zustand

Start by installing Zustand in your Next.js app:

npm install zustand
Enter fullscreen mode Exit fullscreen mode

2. Create a Zustand Store

Zustand allows you to create a store that holds all of your global state. Hereā€™s an example of a store that manages a simple counter.

In Next.js (App Router), itā€™s recommended to keep the store outside the pages or app directory, typically in a lib or stores directory.

Create a store.js file in a lib folder:

import { create } from 'zustand';
// Note: 'create' as a default export is a deprecated import. 


const useStore = create((set, get) => ({
  count: 0,
  increment: () => set((state) => ({ count: get().count + 1 })),
}));

export default useStore;
Enter fullscreen mode Exit fullscreen mode

create is used to define the store.
The store holds the count state, and increment is a function to update the count.

3. Using the Store in the App Router

With Zustand, you can use your store directly in any component or page. Hereā€™s how to set up components to use the store.

Let's define the main page of the app as app/page.tsx for the sake of the example.

import { useStore } from '@/lib/store'; // The store we defined earlier
import Link from 'next/link';

export default function Home() {
  const { count, increment } = useStore();
  return (
    <div>
      <h1>Home Page</h1>
      <p>Count: {count}</p>
      <button onClick={increment}>Increment</button>
      <Link href="/page2">Go to Second Page</Link>
    </div>
  );
};
Enter fullscreen mode Exit fullscreen mode

Since Zustand's store is persistent across pages, we can create another page app/page2.tsx and the state will be kept and change for both pages:

import { useStore } from '@/lib/store' 

export default function SecondPage() {
  const { count, increment } = useStore();
  return (
    <div>
      <h1>Second Page Page</h1>
      <p>Count: {count}</p>
      <button onClick={increment}>Increment</button>
      <Link href="/">Go to Home Page</Link>
    </div>
  );
};

Enter fullscreen mode Exit fullscreen mode

Persisting State with Zustand

You can use Zustand to persist parts of your state across browser sessions. Hereā€™s an example where we persist the darkMode setting to localStorage:

// lib/store.ts
import { create } from 'zustand';
import { persist, createJSONStorage } from 'zustand/middleware'

const usePersistentStore = create(
 persist((set) => ({
    darkMode: false,
    toggleDarkMode: () => set((state) => ({ darkMode: !state.darkMode })),
  }),
  { name: 'persistent-store' } // Keep the store persistent on localStorage, a storage prop is optional (localStorage chosen by default)
 ), 
);

export default useStore;
Enter fullscreen mode Exit fullscreen mode

This way, even after the user refreshes or closes the app, the darkMode state remains in localStorage.

Handling Async Actions with Zustand

You can handle async actions, such as fetching data from an API, by using async functions within your Zustand store. Hereā€™s an example:

// lib/store.ts
import { create }  from 'zustand';

const useStore = create((set) => ({
  data: null,
  fetchData: async () => {
    const response = await fetch('/api/data');
    const result = await response.json();
    set({ data: result });
  },
}));

export default useStore;
Enter fullscreen mode Exit fullscreen mode

Now, you can call fetchData from any component, and Zustand will manage the async state without any extra complexity.

Advanced Store Configuration

Zustand also allows you to create multiple stores for different concerns or use middleware for state persistence, logging, etc. You can encapsulate store logic for better organization in larger apps.

Why Zustand for Next.js?

  1. Simplified State Logic - Zustand is a minimalistic solution that doesnā€™t require defining actions, reducers, or wrapping components with providers. It simplifies the state logic in a way that makes it easy to use in any Next.js app.

  2. Optimized for Performance - Zustand is highly optimized for performance, ensuring that components only re-render when the specific part of the state theyā€™re subscribed to changes. This prevents unnecessary re-renders and keeps your app fast and responsive.

  3. Seamless SSR & SSG Integration - Zustand works seamlessly with Next.js's SSR and SSG features. Since Zustand stores are just JavaScript objects, you can use them directly in both server-side and client-side components without additional configuration.

Conclusion

Zustand is a great state management solution for React and Next.js applications, especially when using the App Router. Its minimalistic design, combined with its easy-to-use API, makes it ideal for both small and large-scale applications. Whether you're building a simple app or a complex system, Zustand allows you to manage state with less boilerplate and better performance.

If youā€™re working with Next.js 13+ and want an efficient way to manage state in your app, Zustand is definitely worth considering. Try integrating it into your projects and let me know how it works for you!

Happy coding (ć£ā—•ā€æā—•)ć£

Top comments (11)

Collapse
 
thomasburleson profile image
Thomas Burleson

What about hydration issues?

Collapse
 
mrsupercraft profile image
MrSuperCraft

Excellent question, Thomas Burleson!

Hydration issues can arise when the store isnā€™t handled properly, particularly if itā€™s initialized and used differently on the client and server. From my experience with Zustand, Iā€™ve never encountered hydration issues caused by Zustand itself. This is because I separate my store implementations on the server side and avoid initializing them directly on the client.

While I donā€™t have personal experience with hydration issues caused by Zustand, Iā€™d recommend keeping your stores in a dedicated folder, such as /src/store or another path that fits your project structure. This practice helps ensure thereā€™s no conflicting state between the client and server.

Thanks for bringing this up!

Collapse
 
skillboosttrainer profile image
SkillBoostTrainer

Zustand makes state management in React & Next.js effortless, with minimal boilerplate and maximum performance. Perfect for modern web apps! šŸš€

Collapse
 
mrsupercraft profile image
MrSuperCraft

Absolutely! Glad to hear you found my article useful šŸ™‚

Collapse
 
anupammaurya profile image
Anupam Maurya

Why persistent, across pages? it could be fetch in source path. Like dark / light theme flag vale.

Collapse
 
mrsupercraft profile image
MrSuperCraft

Hey Anupam Maurya!

The source path fetch is less advisable as things can get quite messy when working with many different variables within the state. The state is persistent and should be that way to provide the user with their selected theme even after leaving the page and returning back to it.
In any case, my example was only to show the option to persist the state across multiple pages and keep it even if the user has quit the page instance. In real cases of implementing dark mode and light mode, I would advise using the next-themes package instead of a persistent state.

Thanks for your question!

Collapse
 
anoff profile image
Andreas Offenhaeuser

When taking a look at the official docs they recommend to initialize the store per request using a provider zustand.docs.pmnd.rs/guides/nextjs

How are you getting around this issue?

Collapse
 
mrsupercraft profile image
MrSuperCraft

Great question, Andreas!

The provider-localized store recommended in the docs is ideal for isolating state per request or page instance, ensuring changes donā€™t carry over unintentionally. This works well for cases like guest-mode demos, where state resets on each session or page.

However, it depends on your use case. In my project CodeLib, I needed a global state to manage data fetched dynamically from an external SDK client (which was used together with a search functionality for all of the user's data). This state had to persist across pages to provide seamless access to code snippets. A provider setup wouldnā€™t work here, as it ties state to a single page.

Provider-localized state is perfect for temporary data, while global state is better for persistent needs like user authentication or shared resources. Both have their placeā€”choose based on your appā€™s requirements.

Let me know if you have anymore questions!

Collapse
 
thunder6230 profile image
thunder6230

Looks pretty good!

Collapse
 
mrsupercraft profile image
MrSuperCraft

Thank you so much! Stay tuned for an interesting article tomorrow as well :)

Collapse
 
onoshe profile image
Onoshe

Ever since I got introduced to Zustand about 2 years ago, it has since being my state management library in all my projects.
I love Zustand