skills/app-store-patterns/SKILL.md
Zustand app store architecture — slice pattern, createXxxSlice factories, AppSlice type, useAppStore selectors, getTypedState for non-React code. Use when adding state to the store, creating new slices, or reading/writing store state in components or hooks.
npx skillsauth add bkinsey808/songshare-effect app-store-patternsInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Requires: file-read, terminal (linting/testing). No network access needed.
Use this skill when:
Execution workflow:
npm run lint.Output requirements:
The app store is a single Zustand store composed from feature slices. Each feature owns its own slice file.
react/src/app-store/
├── AppSlice.type.ts # Intersection of all slice types → AppSlice
├── app-store-types.ts # Set / Get / Api utility types
├── useAppStore.ts # Singleton Zustand hook + getTypedState()
├── config/
│ ├── sliceFactories.ts # Ordered array of createXxxSlice functions
│ └── omittedPersistKeysSet.ts # Keys excluded from localStorage persist
├── hydration.ts # Tracks rehydration state for SSR/hydration wait
└── slice-reset-fns.ts # Registry for resetting all slices
react/src/auth/slice/
├── auth-slice.types.ts # AuthState + AuthSlice types
└── createAuthSlice.ts # Slice factory
Every feature slice follows this structure:
// react/src/<feature>/slice/create<Feature>Slice.ts
import { type Api, type Get, type Set } from "@/react/app-store/app-store-types";
import { sliceResetFns } from "@/react/app-store/slice-reset-fns";
import type { FeatureSlice, FeatureState } from "./<Feature>Slice.type";
const initialState: FeatureState = {
items: [],
isLoading: false,
};
/**
* Create the feature slice for the app store.
*
* @param set - Zustand set function
* @param get - Zustand get function
* @param api - Store API
* @returns The FeatureSlice implementation
*/
export default function createFeatureSlice(
set: Set<FeatureSlice>,
get: Get<FeatureSlice>,
api: Api<FeatureSlice>,
): FeatureSlice {
void get; // silence unused warning if not needed
void api;
// Register reset function so the whole store can be reset
sliceResetFns.add(() => {
set(initialState);
});
return {
...initialState,
setItems: (items) => {
set({ items });
},
fetchItems: async () => {
set({ isLoading: true });
// ... fetch logic
set({ items: result, isLoading: false });
},
};
}
Keep state and actions separated:
// react/src/<feature>/slice/<Feature>Slice.type.ts
export type FeatureState = {
items: readonly Item[];
isLoading: boolean;
};
export type FeatureSlice = FeatureState & {
setItems: (items: readonly Item[]) => void;
fetchItems: () => Promise<void>;
};
react/src/<feature>/slice/create<Feature>Slice.ts and <Feature>Slice.type.tsAppSlice.type.ts:// react/src/app-store/AppSlice.type.ts
import type { FeatureSlice } from "@/react/feature/slice/FeatureSlice.type";
type AppSlice = AuthSlice & /* ... existing ... */ & FeatureSlice;
config/sliceFactories.ts:import createFeatureSlice from "@/react/feature/slice/createFeatureSlice";
const sliceFactories: readonly SliceFactory[] = [
// ...existing...
createFeatureSlice,
];
Use useAppStore with a selector. The selector must be a stable function reference — with React Compiler, plain inline selectors are fine:
import useAppStore from "@/react/app-store/useAppStore";
function MyComponent(): ReactElement {
const items = useAppStore((s) => s.items);
const isLoading = useAppStore((s) => s.isLoading);
// ...
}
For actions (stable references, safe to destructure):
const setItems = useAppStore((s) => s.setItems);
Use getTypedState() for code running outside a component (hooks-driven flows, Effect handlers, test setup):
import { getTypedState } from "@/react/app-store/useAppStore";
const { userSessionData } = getTypedState();
Do not call useAppStore.getState() directly — it returns an untyped shape. getTypedState() centralizes the runtime narrowing.
Use forceCast<T> from @/react/lib/test-utils/forceCast to build typed store stubs without as any:
import useAppStore from "@/react/app-store/useAppStore";
import forceCast from "@/react/lib/test-utils/forceCast";
vi.mock("@/react/app-store/useAppStore");
vi.mocked(useAppStore).mockImplementation((selector) =>
selector(
forceCast<AppSlice>({
items: [],
isLoading: false,
setItems: vi.fn(),
}),
),
);
sliceResetFns.add(...)initialState must be a const separate from the factory return valueSet<FeatureSlice> — not Set<AppSlice> — unless cross-slice state reads are neededuseAppStore.getState() directly — use getTypedState()initialState (it's evaluated at store creation)docs/ai/rules.md.source-refactoring.naming-conventions.tools
Zustand state management patterns for this project — store creation, selectors, Immer middleware, async actions with loading states, devtools, persist, and testing. Use when authoring or editing Zustand stores (use*Store files) or components that subscribe to stores. Do NOT use for React component structure or TypeScript-only utilities.
testing
How to write, update, or split skill files in this repo. Use when creating a new SKILL.md, updating an existing one, or deciding whether to put content in a skill vs. docs/.
development
Complete guide for testing React hooks — renderHook, Documentation by Harness, installStore, fixtures, subscription patterns, lint/compiler traps, and pre-completion checklist. Read docs/testing/unit-test-hook-best-practices.md for the full reference.
development
Vitest unit test authoring for this repo — setup, mocking, API handler testing, and common pitfalls for non-hook code. Use when the user asks to add, update, fix, or review unit tests for utilities, components, API handlers, or scripts. Do NOT use for React hook tests — load unit-test-hook-best-practices instead.