スキル一覧に戻る
imehr

state-management

by imehr

0🍴 0📅 2026年1月15日
GitHubで見るManusで実行

SKILL.md


name: state-management description: State management patterns for React applications version: 1.0.0 type: domain enforcement: suggest priority: high triggers:

  • useState
  • useQuery
  • zustand
  • context
  • global state
  • server state
  • cache
  • TanStack Query

State Management Guidelines

Overview

This skill provides patterns for managing state in React applications, distinguishing between server state (API data) and client state (UI state).

Quick Reference

State TypeToolUse Case
Server StateTanStack QueryAPI data, caching
Global ClientZustandApp settings, UI state
Local ClientuseStateComponent-specific
Shared PropsReact ContextTheme, auth, i18n

The Golden Rule

Server state is NOT client state.

// ❌ WRONG: Treating API data as client state
const [users, setUsers] = useState<User[]>([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState<Error | null>(null);

useEffect(() => {
  setLoading(true);
  fetchUsers()
    .then(setUsers)
    .catch(setError)
    .finally(() => setLoading(false));
}, []);

// ✅ CORRECT: Server state with TanStack Query
const { data: users, isLoading, error } = useQuery({
  queryKey: ['users'],
  queryFn: fetchUsers,
});

Project Configuration

SettingDefaultYour Value
TanStack Query Versionv5CHANGE_ME
Zustand Versionv4CHANGE_ME
Stale Time5 minutesCHANGE_ME
GC Time30 minutesCHANGE_ME

State Decision Tree

Need to manage state?
├── Is it from an API/server?
│   └── YES → TanStack Query
│       └── Handles: caching, loading, error, refetch
│
├── Is it used by multiple components across the app?
│   └── YES → Is it frequently updated?
│       ├── YES → Zustand (subscriptions, selectors)
│       └── NO → React Context (theme, locale, auth)
│
└── Is it local to one component/feature?
    └── YES → useState / useReducer

Core Patterns

Pattern 1: TanStack Query Setup

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

const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      staleTime: 5 * 60 * 1000, // 5 minutes
      gcTime: 30 * 60 * 1000, // 30 minutes (formerly cacheTime)
      retry: 3,
      refetchOnWindowFocus: false,
    },
    mutations: {
      retry: 1,
    },
  },
});

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

Pattern 2: Query Hooks

// ✅ CORRECT: Encapsulated query hook
// hooks/queries/useUsers.ts
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { userApi } from '@/services/api';

export 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: () => userApi.getUsers(filters),
  });
}

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

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

  return useMutation({
    mutationFn: userApi.createUser,
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: userKeys.lists() });
    },
  });
}

Pattern 3: Zustand Store

// ✅ CORRECT: Zustand store with slices
// stores/appStore.ts
import { create } from 'zustand';
import { persist } from 'zustand/middleware';

interface AppState {
  // State
  theme: 'light' | 'dark';
  sidebarOpen: boolean;
  notifications: Notification[];

  // Actions
  setTheme: (theme: 'light' | 'dark') => void;
  toggleSidebar: () => void;
  addNotification: (notification: Notification) => void;
  removeNotification: (id: string) => void;
}

export const useAppStore = create<AppState>()(
  persist(
    (set) => ({
      // Initial state
      theme: 'light',
      sidebarOpen: true,
      notifications: [],

      // Actions
      setTheme: (theme) => set({ theme }),
      toggleSidebar: () => set((state) => ({ sidebarOpen: !state.sidebarOpen })),
      addNotification: (notification) =>
        set((state) => ({
          notifications: [...state.notifications, notification],
        })),
      removeNotification: (id) =>
        set((state) => ({
          notifications: state.notifications.filter((n) => n.id !== id),
        })),
    }),
    {
      name: 'app-storage',
      partialize: (state) => ({ theme: state.theme }), // Only persist theme
    }
  )
);

Pattern 4: Selective Subscriptions

// ✅ CORRECT: Only subscribe to needed state
function ThemeToggle() {
  // Component only re-renders when theme changes
  const theme = useAppStore((state) => state.theme);
  const setTheme = useAppStore((state) => state.setTheme);

  return (
    <button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
      {theme === 'light' ? '🌙' : '☀️'}
    </button>
  );
}

// ❌ WRONG: Subscribing to entire store
function ThemeToggle() {
  // Re-renders on ANY store change
  const { theme, setTheme } = useAppStore();
  // ...
}

Pattern 5: Context for Dependency Injection

// ✅ CORRECT: Context for stable values
// contexts/AuthContext.tsx
interface AuthContextValue {
  user: User | null;
  isAuthenticated: boolean;
  login: (credentials: LoginCredentials) => Promise<void>;
  logout: () => void;
}

const AuthContext = createContext<AuthContextValue | null>(null);

export function AuthProvider({ children }: { children: React.ReactNode }) {
  const [user, setUser] = useState<User | null>(null);

  const value = useMemo(
    () => ({
      user,
      isAuthenticated: !!user,
      login: async (credentials: LoginCredentials) => {
        const user = await authService.login(credentials);
        setUser(user);
      },
      logout: () => {
        authService.logout();
        setUser(null);
      },
    }),
    [user]
  );

  return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
}

export function useAuth() {
  const context = useContext(AuthContext);
  if (!context) {
    throw new Error('useAuth must be used within AuthProvider');
  }
  return context;
}

Anti-patterns Summary

Anti-patternProblemSolution
useState for API dataManual cache, no deduplicationTanStack Query
Full store subscriptionUnnecessary re-rendersSelective selectors
Context for frequent updatesProvider re-renders treeZustand
Props drillingBrittle, hard to maintainContext or Zustand
Redux for everythingBoilerplate overheadRight tool for job

Common Mistakes

// ❌ WRONG: Derived state in store
const useStore = create((set) => ({
  items: [],
  filteredItems: [], // Derived!
  filter: '',
  setFilter: (filter) => set((state) => ({
    filter,
    filteredItems: state.items.filter(i => i.name.includes(filter)),
  })),
}));

// ✅ CORRECT: Compute derived state
const useStore = create((set) => ({
  items: [],
  filter: '',
  setFilter: (filter) => set({ filter }),
}));

// Use selector for derived state
const filteredItems = useStore((state) =>
  state.items.filter((i) => i.name.includes(state.filter))
);

Resources

TopicWhen to ReadLink
TanStack QueryServer state patterns[mdc:resources/tanstack-query.md]
Zustand PatternsClient state patterns[mdc:resources/zustand-patterns.md]
Context PatternsWhen to use Context[mdc:resources/context-patterns.md]
  • frontend-dev-guidelines - Component patterns
  • api-validation - Type-safe API calls
  • testing-guidelines - Testing state hooks

スコア

総合スコア

50/100

リポジトリの品質指標に基づく評価

SKILL.md

SKILL.mdファイルが含まれている

+20
LICENSE

ライセンスが設定されている

0/10
説明文

100文字以上の説明がある

0/10
人気

GitHub Stars 100以上

0/15
最近の活動

3ヶ月以内に更新がある

0/10
フォーク

10回以上フォークされている

0/5
Issue管理

オープンIssueが50未満

+5
言語

プログラミング言語が設定されている

+5
タグ

1つ以上のタグが設定されている

0/5

レビュー

💬

レビュー機能は近日公開予定です