Skip to main content

state-management

Use when choosing state management solutions, implementing global stores (Zustand, Pinia), managing server state (TanStack Query), or handling URL state in frontend applications across React and Vue.

소스 정보

저장소
MadAppGang/claude-code
최근 소스 활동
2026년 1월 20일 08:16
감지된 SKILL.md 언어
영어
스타
287
포크
24

설치 방법

기본적으로 소스를 먼저 확인하는 Prompt가 선택됩니다. 직접 명령으로 전환하거나 로컬 사본을 다운로드할 수도 있습니다.

소스 파일 검토

설치 여부를 결정하기 전에 SKILL.md와 SkillsMP에 표시된 보조 파일을 읽어 보세요.

SKILL.md 표시 중

SKILL.md
소스 지침 · 읽기 전용 미리보기
name
state-management
version
1.0.0
description
Use when choosing state management solutions, implementing global stores (Zustand, Pinia), managing server state (TanStack Query), or handling URL state in frontend applications across React and Vue.
keywords
["state management","Zustand","Pinia","TanStack Query","server state","global state","URL state","React context"]
plugin
dev
updated
2026-01-20T00:00:00.000Z
# Frontend State Management ## Overview Patterns and best practices for managing state in frontend applications across different frameworks. ## State Categories ### Local vs Global State | Type | Scope | Examples | Solution | |------|-------|----------|----------| | **Local UI** | Single component | Form inputs, modals, dropdowns | useState, ref | | **Shared UI** | Component subtree | Theme, sidebar state | Context, provide/inject | | **Server** | Cached API data | Users, products, orders | TanStack Query, SWR | | **Global App** | Entire app | Auth, settings, notifications | Zustand, Pinia, Redux | | **URL** | Browser URL | Filters, pagination, search | Router params/query | ### When to Use What ``` ┌─────────────────────────────────────────────────────────┐ │ Does only this component need it? │ │ YES → Local state (useState/ref) │ │ NO ↓ │ ├─────────────────────────────────────────────────────────┤ │ Is it server data that needs caching/sync? │ │ YES → Server state library (TanStack Query) │ │ NO ↓ │ ├─────────────────────────────────────────────────────────┤ │ Is it in the URL (shareable state)? │ │ YES → URL state (router) │ │ NO ↓ │ ├─────────────────────────────────────────────────────────┤ │ Is it needed across unrelated components? │ │ YES → Global store (Zustand/Pinia) │ │ NO → Lift state up or Context │ └─────────────────────────────────────────────────────────┘ ``` ## Server State (TanStack Query) ### Basic Query Pattern ```tsx // Define query function useUsers(filters: UserFilters) { return useQuery({ queryKey: ['users', filters], queryFn: () => api.getUsers(filters), staleTime: 5 * 60 * 1000, // 5 minutes gcTime: 30 * 60 * 1000, // 30 minutes }); } // Use in component function UserList() { const [filters, setFilters] = useState<UserFilters>({}); const { data, isLoading, error } = useUsers(filters); if (isLoading) return <Spinner />; if (error) return <Error message={error.message} />; return <List items={data} />; } ``` ### Mutation Pattern ```tsx function useCreateUser() { const queryClient = useQueryClient(); return useMutation({ mutationFn: (data: CreateUserInput) => api.createUser(data), onSuccess: () => { // Invalidate and refetch queryClient.invalidateQueries({ queryKey: ['users'] }); }, }); } // Usage const createUser = useCreateUser(); await createUser.mutateAsync({ name: 'John', email: 'john@example.com' }); ``` ### Optimistic Updates ```tsx function useUpdateUser() { const queryClient = useQueryClient(); return useMutation({ mutationFn: (data: UpdateUserInput) => api.updateUser(data), onMutate: async (newData) => { // Cancel outgoing refetches await queryClient.cancelQueries({ queryKey: ['user', newData.id] }); // Snapshot previous value const previous = queryClient.getQueryData(['user', newData.id]); // Optimistically update queryClient.setQueryData(['user', newData.id], (old: User) => ({ ...old, ...newData, })); return { previous }; }, onError: (err, newData, context) => { // Rollback on error queryClient.setQueryData(['user', newData.id], context?.previous); }, onSettled: () => { queryClient.invalidateQueries({ queryKey: ['users'] }); }, }); } ``` ## Global State (Zustand) ### Store Definition ```tsx interface AppStore { // 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<AppStore>((set) => ({ theme: 'light', sidebarOpen: true, notifications: [], 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), })), })); ``` ### Selectors for Performance ```tsx // BAD: Re-renders on any store change const { theme, notifications } = useAppStore(); // GOOD: Only re-renders when theme changes const theme = useAppStore((state) => state.theme); // GOOD: Multiple selectors with shallow comparison const { theme, sidebarOpen } = useAppStore( (state) => ({ theme: state.theme, sidebarOpen: state.sidebarOpen }), shallow ); ``` ### Computed/Derived State ```tsx const useAppStore = create<AppStore>((set, get) => ({ notifications: [], // Derived state as selector unreadCount: () => get().notifications.filter((n) => !n.read).length, // Or compute in selector })); // Usage with computed const unreadCount = useAppStore((state) => state.notifications.filter((n) => !n.read).length ); ``` ## Global State (Pinia for Vue) ```ts export const useAppStore = defineStore('app', () => { // State const theme = ref<'light' | 'dark'>('light'); const sidebarOpen = ref(true); const notifications = ref<Notification[]>([]); // Getters (computed) const unreadCount = computed(() => notifications.value.filter((n) => !n.read).length ); // Actions function setTheme(newTheme: 'light' | 'dark') { theme.value = newTheme; } function toggleSidebar() { sidebarOpen.value = !sidebarOpen.value; } return { theme, sidebarOpen, notifications, unreadCount, setTheme, toggleSidebar, }; }); ``` ## URL State ### Search Params ```tsx // React with react-router function useSearchParams() { const [searchParams, setSearchParams] = useSearchParams(); const filters = useMemo( () => ({ page: parseInt(searchParams.get('page') || '1'), search: searchParams.get('search') || '', sort: searchParams.get('sort') || 'name', }), [searchParams] ); const setFilters = (newFilters: Partial<typeof filters>) => { setSearchParams((prev) => { Object.entries(newFilters).forEach(([key, value]) => { if (value) prev.set(key, String(value)); else prev.delete(key); }); return prev; }); }; return [filters, setFilters] as const; } ``` ### Benefits of URL State - Shareable links - Browser back/forward works - Bookmarkable - SEO-friendly - Survives refresh ## Best Practices ### 1. Colocate State Keep state as close to where it's used as possible. ```tsx // BAD: Global state for local concern const useGlobalStore = create(() => ({ isModalOpen: false, toggleModal: () => {}, })); // GOOD: Local state for local concern function UserProfile() { const [isModalOpen, setModalOpen] = useState(false); } ``` ### 2. Single Source of Truth Don't duplicate state across stores. ```tsx // BAD: Duplicated user in multiple places const authStore = { user: { id: 1, name: 'John' } }; const profileStore = { user: { id: 1, name: 'John' } }; // Duplicated! // GOOD: Reference by ID const authStore = { userId: 1 }; const usersCache = { 1: { id: 1, name: 'John' } }; ``` ### 3. Derive Don't Store Compute derived data instead of storing it. ```tsx // BAD: Storing derived state const store = { items: [], itemCount: 0, // Derived! filteredItems: [], // Derived! totalPrice: 0, // Derived! }; // GOOD: Compute on demand const store = { items: [], }; // Compute when needed const itemCount = items.length; const filteredItems = items.filter(predicate); const totalPrice = items.reduce((sum, i) => sum + i.price, 0); ``` ### 4. Normalize Complex Data ```tsx // BAD: Nested data const store = { orders: [ { id: 1, user: { id: 1, name: 'John' }, items: [{ id: 1, product: { id: 1, name: 'Widget' } }], }, ], }; // GOOD: Normalized const store = { orders: { 1: { id: 1, userId: 1, itemIds: [1] } }, users: { 1: { id: 1, name: 'John' } }, items: { 1: { id: 1, productId: 1, orderId: 1 } }, products: { 1: { id: 1, name: 'Widget' } }, }; ``` ### 5. Handle Loading/Error States ```tsx interface AsyncState<T> { data: T | null; loading: boolean; error: Error | null; } // Always handle all three states function UserList({ state }: { state: AsyncState<User[]> }) { if (state.loading) return <Spinner />; if (state.error) return <Error error={state.error} />; if (!state.data?.length) return <Empty />; return <List items={state.data} />; } ``` ## Anti-Patterns ### 1. Prop Drilling (Instead: Context or Store) ```tsx // BAD: Passing through many levels <App theme={theme}> <Layout theme={theme}> <Sidebar theme={theme}> <MenuItem theme={theme} /> // 4 levels deep! </Sidebar> </Layout> </App> // GOOD: Context or store const theme = useTheme(); // Access anywhere ``` ### 2. Storing Server Data in Global State ```tsx // BAD: Manual cache management const store = { users: [], fetchUsers: async () => { const users = await api.getUsers(); set({ users }); }, }; // GOOD: Use server state library const { data: users } = useQuery(['users'], api.getUsers); ``` ### 3. Mutating State Directly ```tsx // BAD: Direct mutation state.users.push(newUser); state.users[0].name = 'Updated'; // GOOD: Immutable updates set({ users: [...state.users, newUser] }); set({ users: state.users.map((u) => u.id === id ? { ...u, name: 'Updated' } : u ), }); ``` --- *State management patterns for frontend applications*
GitHub에서 보기