Since we’re exploring Next.js lately, let’s see how we achieve client-side state management in React or Next.js. For basic level of state management such as defining a component level variable, we normally use React’s useState hook. For more advanced use cases, we have dedicated libraries such as Redux or Zustand. In this tutorial we’ll use Zustand within a Next.js app in order to incorporate client-side state management which is persisted app-wide.
Assuming you have Node.js installed in your system, enter following command in terminal to create a Next.js app:
npx create-next-app@latest
Keep pressing Enter to select the default options and you are good to go. Once the installation is finished, go inside the newly created folder my-app:
cd my-app
Now, install Zustand by entering following command:
npm install zustand
At this stage (or later), you may execute following command to run development server:
npm run dev
Open your browser and enter following URL to check everything is working fine:
localhost:3000
At this point, you may also choose to open my-app folder in your favorite code editor.
Now, let’s create the folder stores by executing following command inside my-app folder:
mkdir stores
We’ll be using this stores folder to keep Zustand related stuff. So, create a new file store.ts inside stores folder by executing:
touch stores/store.ts
and then insert following code into stores/store.ts:
import {create} from 'zustand'
import { persist } from 'zustand/middleware'
interface countState {
counter: number;
oneUp: () => void;
reset: () => void;
}
const useCounter = create<countState>()(
persist(
(set, get) => ({
counter: 0,
oneUp: () => set({ counter: get().counter + 1 }),
reset: () => set({ counter:0 }),
}),
{
name: 'my-store',
}
)
)
export default useCounter
Next, create another file useStore.ts inside stores folder:
touch stores/useStore.ts
and then insert following code inside stores/useStore.ts:
import { useState, useEffect } from 'react'
const useStore = <T, F>(
store: (callback: (state: T) => unknown) => unknown,
callback: (state: T) => F
) => {
const result = store(callback) as F
const [data, setData] = useState<F>()
useEffect(() => {
setData(result)
}, [result])
return data
}
export default useStore
All set…we’ve defined persistent data at the client-side using Zustand library. Let’s check it now – so, open app/page.tsx and replace all the code inside it with following:
'use client'
import useCounter from '@/stores/store'
import useStore from '@/stores/useStore'
export default function Home() {
const counter = useStore(useCounter, (state) => state.counter)
const oneUp= useCounter((state) => state.oneUp)
const reset= useCounter((state) => state.reset)
return (
<main className="flex min-h-screen flex-col items-center p-4">
<p className="p-10 bg-zinc-600 text-white font-bold py-2 px-4 rounded">
Welcome on Homepage
</p>
<div className="w-full max-w-5xl items-center justify-between font-mono text-sm">
<div className="m-2">
There are {counter} items.
</div>
<button onClick={oneUp} className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded m-2">one up</button>
<button onClick={reset} className="bg-red-500 hover:bg-red-700 text-white font-bold py-2 px-4 rounded m-2">Remove All</button>
</div>
</main>
)
}
Now, head to browser and enter following URL assuming dev server is already running:
localhost:3000
Add some counts, close browser, reopen browser and enter the above URL again. To check whether the store’s data is persisting app-wide, let’s create another route inside our Next.js app by executing following command:
mkdir app/second
Now, create page.tsx inside app/second:
touch app/second/page.tsx
And then insert following code inside app/second/page.tsx:
'use client'
import useCounter from '@/stores/store'
import useStore from '@/stores/useStore'
export default function Second() {
const counter = useStore(useCounter, (state) => state.counter)
const oneUp= useCounter((state) => state.oneUp)
const reset= useCounter((state) => state.reset)
return (
<main className="flex min-h-screen flex-col items-center p-4">
<p className="p-10 bg-lime-600 text-white font-bold py-2 px-4 rounded">
This is Second Page
</p>
<div className="w-full max-w-5xl items-center justify-between font-mono text-sm">
<div className="m-2">
There are {counter} items.
</div>
<button onClick={oneUp} className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded m-2">one up</button>
<button onClick={reset} className="bg-red-500 hover:bg-red-700 text-white font-bold py-2 px-4 rounded m-2">Remove All</button>
</div>
</main>
)
}
Save and then enter following URL in the browser:
localhost:3000/second
Observe that the store’s data is persisting app-wide.