From dc377a24129b8517eba570aa2be5087039f26513 Mon Sep 17 00:00:00 2001 From: "tembo[bot]" <208362400+tembo[bot]@users.noreply.github.com> Date: Tue, 25 Nov 2025 22:14:59 +0000 Subject: [PATCH] feat(zustand): Set up Zustand state management Co-authored-by: Jean --- apps/web/package.json | 3 +- apps/web/src/app/providers.tsx | 5 ++- .../web/src/stores/counter-store-provider.tsx | 43 +++++++++++++++++++ apps/web/src/stores/counter-store.ts | 24 +++++++++++ bun.lock | 6 +++ 5 files changed, 79 insertions(+), 2 deletions(-) create mode 100644 apps/web/src/stores/counter-store-provider.tsx create mode 100644 apps/web/src/stores/counter-store.ts diff --git a/apps/web/package.json b/apps/web/package.json index 284c14d6..4477ccd5 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -120,7 +120,8 @@ "use-stick-to-bottom": "^1.1.1", "usehooks-ts": "^3.1.1", "xstate": "^5.24.0", - "zod": "^4.1.5" + "zod": "^4.1.5", + "zustand": "^5.0.8" }, "devDependencies": { "@eslint/eslintrc": "^3.3.1", diff --git a/apps/web/src/app/providers.tsx b/apps/web/src/app/providers.tsx index 6fd3d0e8..61a094f6 100644 --- a/apps/web/src/app/providers.tsx +++ b/apps/web/src/app/providers.tsx @@ -7,6 +7,7 @@ import { NuqsAdapter } from "nuqs/adapters/next/app"; import { jotaiStore } from "@/atoms/store"; import { ThemeProvider } from "@/components/ui/theme-provider"; import { TRPCReactProvider } from "@/lib/trpc/client"; +import { CounterStoreProvider } from "@/stores/counter-store-provider"; export function Providers(props: Readonly<{ children: ReactNode }>) { return ( @@ -18,7 +19,9 @@ export function Providers(props: Readonly<{ children: ReactNode }>) { disableTransitionOnChange > - {props.children} + + {props.children} + diff --git a/apps/web/src/stores/counter-store-provider.tsx b/apps/web/src/stores/counter-store-provider.tsx new file mode 100644 index 00000000..ab9dc43e --- /dev/null +++ b/apps/web/src/stores/counter-store-provider.tsx @@ -0,0 +1,43 @@ +"use client"; + +import { type ReactNode, createContext, useRef, useContext } from "react"; +import { useStore } from "zustand"; + +import { + type CounterStore, + createCounterStore, + defaultInitState, +} from "@/stores/counter-store"; + +export type CounterStoreApi = ReturnType; + +export const CounterStoreContext = createContext( + undefined +); + +export interface CounterStoreProviderProps { + children: ReactNode; +} + +export const CounterStoreProvider = ({ children }: CounterStoreProviderProps) => { + const storeRef = useRef(null); + if (storeRef.current === null) { + storeRef.current = createCounterStore(defaultInitState); + } + + return ( + + {children} + + ); +}; + +export const useCounterStore = (selector: (store: CounterStore) => T): T => { + const counterStoreContext = useContext(CounterStoreContext); + + if (!counterStoreContext) { + throw new Error("useCounterStore must be used within CounterStoreProvider"); + } + + return useStore(counterStoreContext, selector); +}; diff --git a/apps/web/src/stores/counter-store.ts b/apps/web/src/stores/counter-store.ts new file mode 100644 index 00000000..af52e471 --- /dev/null +++ b/apps/web/src/stores/counter-store.ts @@ -0,0 +1,24 @@ +import { createStore } from "zustand/vanilla"; + +export type CounterState = { + count: number; +}; + +export type CounterActions = { + decrementCount: () => void; + incrementCount: () => void; +}; + +export type CounterStore = CounterState & CounterActions; + +export const defaultInitState: CounterState = { + count: 0, +}; + +export const createCounterStore = (initState: CounterState = defaultInitState) => { + return createStore()((set) => ({ + ...initState, + decrementCount: () => set((state) => ({ count: state.count - 1 })), + incrementCount: () => set((state) => ({ count: state.count + 1 })), + })); +}; diff --git a/bun.lock b/bun.lock index b43c8210..3498607f 100644 --- a/bun.lock +++ b/bun.lock @@ -126,6 +126,7 @@ "usehooks-ts": "^3.1.1", "xstate": "^5.24.0", "zod": "^4.1.5", + "zustand": "^5.0.8", }, "devDependencies": { "@eslint/eslintrc": "^3.3.1", @@ -241,6 +242,9 @@ "@repo/env": "workspace:*", "postgres": "^3.4.7", "server-only": "^0.0.1", + "superjson": "^2.2.2", + "superjson-temporal": "^0.4.0", + "temporal-polyfill": "^0.3.0", }, "devDependencies": { "@repo/eslint-config": "workspace:*", @@ -3216,6 +3220,8 @@ "zod-validation-error": ["zod-validation-error@4.0.2", "", { "peerDependencies": { "zod": "^3.25.0 || ^4.0.0" } }, "sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ=="], + "zustand": ["zustand@5.0.8", "", { "peerDependencies": { "@types/react": ">=18.0.0", "immer": ">=9.0.6", "react": ">=18.0.0", "use-sync-external-store": ">=1.2.0" }, "optionalPeers": ["@types/react", "immer", "react", "use-sync-external-store"] }, "sha512-gyPKpIaxY9XcO2vSMrLbiER7QMAMGOQZVRdJ6Zi782jkbzZygq5GI9nG8g+sMgitRtndwaBSl7uiqC49o1SSiw=="], + "zwitch": ["zwitch@2.0.4", "", {}, "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A=="], "@babel/core/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],