zustand-tanstack
Zustand + TanStack Query 상태 관리 패턴. store 생성, devtools, 서버 상태 연동, 디렉토리 구조.
Install with Codex or Claude Copy this prompt, paste it into Codex, Claude, or another assistant, and let it review the skill page and install it for you.
Menu
Zustand + TanStack Query 상태 관리 패턴. store 생성, devtools, 서버 상태 연동, 디렉토리 구조.
Install with Codex or Claude Copy this prompt, paste it into Codex, Claude, or another assistant, and let it review the skill page and install it for you.
Based on SOC occupation classification
세션 핸드오프. 컨텍스트가 차기 전에 중요 맥락을 progress.md에 문서화하고 새 세션 킥오프 프롬프트를 클립보드에 복사한다. "핸드오프", "세션 정리하고 넘기자", "컨텍스트 정리", "이어갈 준비" 등의 요청에 사용.
MD 파일 또는 텍스트로 작업을 정의하면, 분석 → 디자인 확인 → 구현 → 검증 → 커밋 → PR까지 전체 플로우를 수행한다. "이거 만들어줘", "기능 구현해줘", "작업 시작하자", "이 티켓 진행해줘" 등 코드 작업 착수 발화에 사용.
Claude + OpenAI Codex 협업 스킬. MCP 서버 설정 시에만 사용 가능.
code-forge 상태 대시보드. REFLECT flag, quality 이벤트, notepad/decisions, usage 집계를 한 번에. forge-glow 같은 외부 도구는 --json으로 파싱.
교차 모델 토론. Agent Teams / Codex CLI / self-debate 모드 선택. 설계 결정, 아키텍처 선택 시 활용. "어느 쪽이 나을까", "설계 비교해줘", "토론시켜줘", "교차 검증해줘" 등의 요청에 사용.
화면 단위 E2E 테스트 자동화. Figma/코드 기반 테스트 케이스 도출 → Playwright 코드 생성 → Forge Loop(에스컬레이션 기반 자율 실행). "E2E 돌려줘", "화면 테스트 만들어줘", "이 페이지 자동 테스트" 등의 요청에 사용.
| name | zustand-tanstack |
| description | Zustand + TanStack Query 상태 관리 패턴. store 생성, devtools, 서버 상태 연동, 디렉토리 구조. |
| 상태 유형 | 도구 | 예시 |
|---|---|---|
| 서버 상태 | TanStack Query | 주문 목록, 사용자 정보 |
| 전역 클라이언트 상태 | Zustand | 선택된 탭, 사이드바 열림 여부 |
| 폼 상태 | React Hook Form | 입력값, 유효성 검사 |
| 로컬 UI 상태 | useState | 모달 열림/닫힘 |
store/
├── useAuthStore.ts # 인증 상태
├── useUIStore.ts # UI 전역 상태
└── useFilterStore.ts # 필터/검색 상태
queries/
├── order/
│ ├── index.ts # useOrderQuery, useOrderMutation
│ └── queryKeys.ts # 쿼리 키 팩토리
└── user/
├── index.ts
└── queryKeys.ts
// store/useAuthStore.ts
import { create } from 'zustand';
import { devtools, persist } from 'zustand/middleware';
interface User {
id: string;
name: string;
email: string;
}
interface AuthState {
user: User | null;
isAuthenticated: boolean;
// Actions
login: (user: User) => void;
logout: () => void;
}
export const useAuthStore = create<AuthState>()(
devtools(
persist(
(set) => ({
user: null,
isAuthenticated: false,
login: (user) => set({ user, isAuthenticated: true }, false, 'login'),
logout: () => set({ user: null, isAuthenticated: false }, false, 'logout'),
}),
{
name: 'auth-storage', // localStorage key
partialize: (state) => ({ user: state.user }), // 저장할 필드만 선택
},
),
{ name: 'AuthStore' }, // DevTools 이름
),
);
// store/useUIStore.ts
import { create } from 'zustand';
import { devtools } from 'zustand/middleware';
interface UIState {
isSidebarOpen: boolean;
activeTab: string;
// Actions
toggleSidebar: () => void;
setActiveTab: (tab: string) => void;
}
export const useUIStore = create<UIState>()(
devtools(
(set) => ({
isSidebarOpen: true,
activeTab: 'overview',
toggleSidebar: () =>
set((state) => ({ isSidebarOpen: !state.isSidebarOpen }), false, 'toggleSidebar'),
setActiveTab: (tab) =>
set({ activeTab: tab }, false, 'setActiveTab'),
}),
{ name: 'UIStore' },
),
);
// ✅ 좋은 예: selector로 필요한 상태만 구독
function Header() {
const user = useAuthStore((state) => state.user);
const logout = useAuthStore((state) => state.logout);
return <div>{user?.name} <button onClick={logout}>로그아웃</button></div>;
}
// ❌ 나쁜 예: 전체 state 구독 (불필요한 리렌더링 발생)
function Header() {
const { user, logout } = useAuthStore();
}
// ✅ 여러 값을 한번에 구독할 때 (shallow 비교)
import { useShallow } from 'zustand/react/shallow';
function FilterPanel() {
const { status, dateRange, setStatus } = useFilterStore(
useShallow((state) => ({
status: state.status,
dateRange: state.dateRange,
setStatus: state.setStatus,
})),
);
}
// queries/order/queryKeys.ts
export const orderKeys = {
all: ['orders'] as const,
lists: () => [...orderKeys.all, 'list'] as const,
list: (filters: OrderFilters) => [...orderKeys.lists(), filters] as const,
details: () => [...orderKeys.all, 'detail'] as const,
detail: (id: string) => [...orderKeys.details(), id] as const,
};
// queries/order/index.ts
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { orderKeys } from './queryKeys';
import { fetchOrders, fetchOrder, createOrder, updateOrder } from '@/services/order';
export function useOrderListQuery(filters: OrderFilters) {
return useQuery({
queryKey: orderKeys.list(filters),
queryFn: () => fetchOrders(filters),
staleTime: 5 * 60 * 1000, // 5분
});
}
export function useOrderDetailQuery(id: string) {
return useQuery({
queryKey: orderKeys.detail(id),
queryFn: () => fetchOrder(id),
enabled: !!id, // id가 있을 때만 실행
});
}
export function useCreateOrderMutation() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: createOrder,
onSuccess: () => {
// 목록 캐시 무효화
queryClient.invalidateQueries({ queryKey: orderKeys.lists() });
},
});
}
// Zustand 필터 상태 + TanStack Query 서버 상태 조합
function OrderListPage() {
// 클라이언트 상태 (Zustand)
const filters = useFilterStore(useShallow((s) => ({
status: s.status,
dateRange: s.dateRange,
})));
// 서버 상태 (TanStack Query)
const { data, isLoading } = useOrderListQuery(filters);
return <OrderList orders={data?.orders} loading={isLoading} />;
}
// src/main.tsx 또는 providers/QueryProvider.tsx
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 60 * 1000, // 1분
retry: 1,
refetchOnWindowFocus: false,
},
},
});
function Providers({ children }: { children: React.ReactNode }) {
return (
<QueryClientProvider client={queryClient}>
{children}
<ReactQueryDevtools initialIsOpen={false} />
</QueryClientProvider>
);
}
devtools 미들웨어 적용?useShallow 사용?queryKeys 팩토리로 관리?invalidateQueries 처리?