agentleFS
PMDevSolutionsPMDevSolutions/Aurelius/.claude/skills/state-management/SKILL.md

state-management

Skill9 starsChanged 20 days agoMITView on GitHub

State management patterns for React apps — Zustand, TanStack Query, Context, URL state. Guides architecture decisions for local, global, server, and URL state. Keywords: state management, zustand, tanstack query, react query, global state, server state, data fetching, caching

  • Installs packages

27 other projects use a version of this, 1 of them edited.

Adopted by

Most recently taken up by

Used with

Projects with it also use css-architecture, error-handling and 10 more.

Other files people use alongside this one.

Files found in the same public projects as a version of this one, more often than they turn up anywhere else. Each project counts once, large collections count for less, and this file's own repository is left out.
  • Piyush8296Skill

    css-architecture

    CSS architecture, Tailwind patterns, responsive design, design tokens, and styling best practices. Use when implementing designs, setting up styling infrastructure, or debugging layout issues.

    Also in 3 projects that use this

  • MicrosoftSkill

    error-handling

    统一错误处理系统。在添加 API 端点、修改错误处理、添加前端 API 调用、编写错误相关测试时使用。

    Also in 6 projects that use this

  • lbednerSkill

    i18n

    Use when a code or template change introduces a new translation message key, in either the framework CLI (aegis/i18n/locales/) or a generated project's CLI (the app/i18n/locales/ template tree). Covers adding the real English string, stubbing the same key into every other locale for parity, and the separate translation PR that later replaces placeholders with reviewed native text.

    Also in 4 projects that use this

  • mijunriSkill

    database

    数据库脚本与操作规范。脚本放在 db_scripts 目录,遵循查询优化、事务、安全等实践。

    Also in 3 projects that use this

  • genomewalkerSkill

    resume

    Resume a thread by loading its ~800-token context capsule

    Also in 3 projects that use this

  • NVIDIASkill

    testing

    Run Python and Rust tests for physicsnemo-curator using uv + pytest with coverage reporting via pytest-cov, and cargo-nextest for Rust tests. Includes benchmark workflows with pytest-benchmark and criterion.

    Also in 6 projects that use this

The file

We split this skill into the rules it gives an agent

11 rules. 0 are common, 6 are distinctive, and 5 are very specific to this project.

  • 0

    Common

    Most of the 21 edited versions we read keep these.

  • 6

    Distinctive

    This author's own choices. Worth a look.

  • 5

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Very specific:

    Rule: If you have 3+ related useState calls that change together, switch to useReducer.

    line 65
  2. Distinctive:

    Always use selectors for reads

    line 118
  3. Very specific:

    Use persist middleware for state that survives refresh (theme, user preferences)

    line 119
  4. Very specific:

    Use devtools middleware in development

    line 120
  5. Distinctive:

    Keep actions in the store, not in components

    line 121
  6. Distinctive:

    Never store server data in Zustand — use TanStack Query

    line 122
  7. Distinctive:

    Use for: All data from APIs. Handles caching, refetching, loading states, error states, optimistic updates.

    line 126
  8. Distinctive:

    NEVER store API data in useState or Zustand

    line 216
  9. Distinctive:

    Every API call gets its own custom hook

    line 217
  10. Very specific:

    Use enabled to conditionally fetch

    line 219
  11. Very specific:

    Use staleTime (not refetchInterval) for most cases

    line 220

“Common” is measured: kept by at least 6 in 10 of the 21 edited versions of this file we could read. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

State Management Patterns

When to Use This Skill

Use when:

  • Setting up state management for a new React app
  • Deciding between state solutions
  • Implementing data fetching/caching
  • Debugging stale state or unnecessary re-renders
  • Migrating from Redux or other state libraries

State Categories Decision Tree

What kind of state?
├── UI State (local) → useState / useReducer
│   └── Shared across 2-3 components → Lift state up
├── UI State (global) → Zustand
│   └── Theme, sidebar open, modals, toasts
├── Server State → TanStack Query
│   └── API data, caching, background refetching
├── URL State → useSearchParams / nuqs
│   └── Filters, pagination, sort, tab selection
└── Form State → React Hook Form + Zod
    └── Inputs, validation, submission

1. Local State — useState / useReducer

Use for: Component-scoped UI state that doesn't need sharing.

// Simple toggle
const [isOpen, setIsOpen] = useState(false);

// Complex state with multiple transitions — use useReducer
type Action =
  | { type: 'FETCH_START' }
  | { type: 'FETCH_SUCCESS'; data: Item[] }
  | { type: 'FETCH_ERROR'; error: string };

interface State {
  items: Item[];
  loading: boolean;
  error: string | null;
}

function reducer(state: State, action: Action): State {
  switch (action.type) {
    case 'FETCH_START':
      return { ...state, loading: true, error: null };
    case 'FETCH_SUCCESS':
      return { items: action.data, loading: false, error: null };
    case 'FETCH_ERROR':
      return { ...state, loading: false, error: action.error };
  }
}

Rule: If you have 3+ related useState calls that change together, switch to useReducer.

2. Global UI State — Zustand

Use for: State shared across many components (theme, auth status, notifications, layout).

Setup

pnpm add zustand

Store Pattern

// src/stores/use-app-store.ts
import { create } from 'zustand';
import { devtools, persist } from 'zustand/middleware';

interface AppState {
  theme: 'light' | 'dark';
  sidebarOpen: boolean;
  setTheme: (theme: 'light' | 'dark') => void;
  toggleSidebar: () => void;
}

export const useAppStore = create<AppState>()(
  devtools(
    persist(
      (set) => ({
        theme: 'light',
        sidebarOpen: true,
        setTheme: (theme) => set({ theme }),
        toggleSidebar: () => set((s) => ({ sidebarOpen: !s.sidebarOpen })),
      }),
      { name: 'app-store' }
    )
  )
);

Usage — Select Only What You Need

// GOOD — component re-renders only when theme changes
const theme = useAppStore((s) => s.theme);

// BAD — re-renders on ANY store change
const { theme } = useAppStore();

Zustand Rules

  • One store per domain (app, auth, notifications) — not one mega store
  • Always use selectors for reads
  • Use persist middleware for state that survives refresh (theme, user preferences)
  • Use devtools middleware in development
  • Keep actions in the store, not in components
  • Never store server data in Zustand — use TanStack Query

3. Server State — TanStack Query

Use for: All data from APIs. Handles caching, refetching, loading states, error states, optimistic updates.

Setup

pnpm add @tanstack/react-query @tanstack/react-query-devtools

Provider Setup

// src/providers/query-provider.tsx
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';

const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      staleTime: 60 * 1000,
      gcTime: 5 * 60 * 1000,
      retry: 1,
      refetchOnWindowFocus: false,
    },
  },
});

export function QueryProvider({ children }: { children: React.ReactNode }) {
  return (
    <QueryClientProvider client={queryClient}>
      {children}
      <ReactQueryDevtools initialIsOpen={false} />
    </QueryClientProvider>
  );
}

Query Pattern — Custom Hooks

// src/hooks/use-users.ts
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';

const userKeys = {
  all: ['users'] as const,
  lists: () => [...userKeys.all, 'list'] as const,
  list: (filters: UserFilters) => [...userKeys.lists(), filters] as const,
  details: () => [...userKeys.all, 'detail'] as const,
  detail: (id: string) => [...userKeys.details(), id] as const,
};

export function useUsers(filters: UserFilters) {
  return useQuery({
    queryKey: userKeys.list(filters),
    queryFn: () => api.users.list(filters),
  });
}

export function useUser(id: string) {
  return useQuery({
    queryKey: userKeys.detail(id),
    queryFn: () => api.users.get(id),
    enabled: !!id,
  });
}

export function useUpdateUser() {
  const queryClient = useQueryClient();

  return useMutation({
    mutationFn: (data: UpdateUserInput) => api.users.update(data),
    onMutate: async (newData) => {
      await queryClient.cancelQueries({ queryKey: userKeys.detail(newData.id) });
      const previous = queryClient.getQueryData(userKeys.detail(newData.id));
      queryClient.setQueryData(userKeys.detail(newData.id), (old: User) => ({
        ...old,
        ...newData,
      }));
      return { previous };
    },
    onError: (_err, newData, context) => {
      queryClient.setQueryData(userKeys.detail(newData.id), context?.previous);
    },
    onSettled: (_data, _err, variables) => {
      queryClient.invalidateQueries({ queryKey: userKeys.detail(variables.id) });
    },
  });
}

TanStack Query Rules

  • NEVER store API data in useState or Zustand
  • Every API call gets its own custom hook
  • Query keys are structured objects (not strings)
  • Use enabled to conditionally fetch
  • Use staleTime (not refetchInterval) for most cases
  • Optimistic updates for mutations that need instant feedback
  • Invalidate related queries after mutations

4. URL State — Search Params

Use for: Filters, pagination, sort order, active tabs — anything the user should be able to bookmark or share.

Next.js (App Router)

'use client';
import { useSearchParams, useRouter, usePathname } from 'next/navigation';

export function useUrlState<T extends string>(key: string, defaultValue: T) {
  const searchParams = useSearchParams();
  const router = useRouter();
  const pathname = usePathname();

  const value = (searchParams.get(key) as T) ?? defaultValue;

  const setValue = (newValue: T) => {
    const params = new URLSearchParams(searchParams.toString());
    if (newValue === defaultValue) {
      params.delete(key);
    } else {
      params.set(key, newValue);
    }
    router.replace(`${pathname}?${params.toString()}`);
  };

  return [value, setValue] as const;
}

Vite (with nuqs)

pnpm add nuqs
import { useQueryState, parseAsInteger } from 'nuqs';

function ProductList() {
  const [search, setSearch] = useQueryState('q', { defaultValue: '' });
  const [page, setPage] = useQueryState('page', parseAsInteger.withDefault(1));
  const [sort, setSort] = useQueryState('sort', { defaultValue: 'newest' });
}

Anti-Patterns to Avoid

Anti-PatternInstead
Storing API data in useStateUse TanStack Query
Prop drilling through 4+ levelsZustand store or composition
Context for frequently changing valuesZustand (Context causes full tree re-render)
One giant Redux/Zustand storeSplit by domain
useEffect to sync stateDerive it or use useMemo
Fetching in useEffectTanStack Query
Manual loading/error statesTanStack Query gives these for free
Duplicating server state in clientSingle source: TanStack Query cache

Testing State

Zustand — test the store directly

import { useAppStore } from '@/stores/use-app-store';

beforeEach(() => {
  useAppStore.setState({ theme: 'light', sidebarOpen: true });
});

test('toggleSidebar flips the value', () => {
  useAppStore.getState().toggleSidebar();
  expect(useAppStore.getState().sidebarOpen).toBe(false);
});

TanStack Query — use wrapper

import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { renderHook, waitFor } from '@testing-library/react';

function createWrapper() {
  const queryClient = new QueryClient({
    defaultOptions: { queries: { retry: false } },
  });
  return ({ children }: { children: React.ReactNode }) => (
    <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
  );
}

test('useUsers returns user list', async () => {
  const { result } = renderHook(() => useUsers({}), { wrapper: createWrapper() });
  await waitFor(() => expect(result.current.isSuccess).toBe(true));
  expect(result.current.data).toHaveLength(3);
});

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.