Docs menu

State store

Zustand and Redux — files and usage, separated by Vite/React vs Next.

Choose None, Zustand, or Redux Toolkit. Patterns below are split by stack.

Zustand

React

Vite SPA & React Router

File injected:

textsrc/app/store/useCounterStore.ts
src/app/store/useCounterStore.ts

No provider — import the hook anywhere. Persist key: react-dose-counter-storage.

tsxexample.tsx
import { useCounterStore } from "@/app/store/useCounterStore";

export function Counter() {
  const count = useCounterStore((s) => s.count);
  const increment = useCounterStore((s) => s.increment);

  return (
    <button type="button" onClick={increment}>
      {count}
    </button>
  );
}
Next.js

Next.js

Same store file and API as Vite. Still no provider — it is a client hook with localStorage persist. Use it from Client Components.

tsxCounter.tsx
"use client";

import { useCounterStore } from "@/app/store/useCounterStore";

export function Counter() {
  const count = useCounterStore((s) => s.count);
  const increment = useCounterStore((s) => s.increment);
  return <button onClick={increment}>{count}</button>;
}

Redux Toolkit

React

Vite SPA & React Router

Files injected:

text
src/app/store/store.ts
src/app/store/hooks.ts
src/app/store/slices/counterSlice.ts
src/app/providers/redux-provider.tsx

Singleton store. Nested in RootProvider as <ReduxProvider>.

tsxexample.tsx
import { useAppDispatch, useAppSelector } from "@/app/store/hooks";
import { increment } from "@/app/store/slices/counterSlice";

export function Counter() {
  const count = useAppSelector((s) => s.counter.count);
  const dispatch = useAppDispatch();

  return (
    <button type="button" onClick={() => dispatch(increment())}>
      {count}
    </button>
  );
}
Next.js

Next.js

Same file set, but store creation is Next-safe:

  • makeStore() instead of a module singleton
  • Client redux-provider keeps one store in useRef
  • Still nested under RootProvider

Usage of useAppDispatch / useAppSelector is the same in Client Components.