---
name: react-state-management
description: State management patterns với Zustand và TanStack Query cho React/Next.js. Use for client state, server state, caching, và optimistic updates.
compatibility: "zustand>=5, @tanstack/react-query>=5, react>=19"
metadata:
  author: Velik Ho
  version: "1.0"
---

# React State Management / Quản Lý State React

## When to Use This Skill / Khi Nào Sử Dụng

Use this skill when:
- Creating Zustand stores / Tạo Zustand stores
- Implementing TanStack Query / Triển khai TanStack Query
- Managing client vs server state / Quản lý client vs server state
- Implementing optimistic updates / Triển khai optimistic updates
- Testing stores and queries / Testing stores và queries

## Core Concepts / Khái Niệm Cốt Lõi

### State Categories

| Category | Solution | Use Case |
|----------|----------|----------|
| **UI State** | Zustand | Theme, modals, local preferences |
| **Server State** | TanStack Query | API data, caching, sync |
| **URL State** | Next.js Router | Filters, pagination, search |
| **Form State** | React Hook Form | Form inputs, validation |

## Key Patterns / Mẫu Chính

### Zustand Store Pattern

```typescript
// stores/auth-store.ts
import { create } from 'zustand';
import { devtools, persist } from 'zustand/middleware';

/**
 * EN: Auth store interface
 * VI: Interface cho auth store
 */
interface AuthState {
  user: User | null;
  isAuthenticated: boolean;
  // Actions
  login: (user: User) => void;
  logout: () => void;
}

/**
 * EN: Create store with middleware
 * VI: Tạo store với middleware
 */
export const useAuthStore = create<AuthState>()(
  devtools(
    persist(
      (set) => ({
        user: null,
        isAuthenticated: false,
        
        login: (user) => set({ user, isAuthenticated: true }, false, 'auth/login'),
        logout: () => set({ user: null, isAuthenticated: false }, false, 'auth/logout'),
      }),
      { name: 'auth-storage' }
    ),
    { name: 'AuthStore' }
  )
);
```

### Selector Pattern (Performance)

```typescript
// ✅ GOOD: Use selectors to prevent unnecessary re-renders
// VI: Sử dụng selectors để tránh re-render không cần thiết
function UserAvatar() {
  // Only re-renders when user.avatar changes
  const avatar = useAuthStore((state) => state.user?.avatar);
  return <Avatar src={avatar} />;
}

// ❌ BAD: Subscribes to entire store
function UserAvatar() {
  const { user } = useAuthStore(); // Re-renders on any store change
  return <Avatar src={user?.avatar} />;
}
```

### TanStack Query Pattern

```typescript
// services/api.ts
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';

/**
 * EN: Query keys factory for type-safe keys
 * VI: Factory cho query keys đảm bảo type-safe
 */
export const chatKeys = {
  all: ['chats'] as const,
  lists: () => [...chatKeys.all, 'list'] as const,
  list: (filters: ChatFilters) => [...chatKeys.lists(), filters] as const,
  details: () => [...chatKeys.all, 'detail'] as const,
  detail: (id: string) => [...chatKeys.details(), id] as const,
};

/**
 * EN: Custom hook for fetching chats
 * VI: Custom hook để fetch chats
 */
export function useChats(filters: ChatFilters) {
  return useQuery({
    queryKey: chatKeys.list(filters),
    queryFn: () => fetchChats(filters),
    staleTime: 5 * 60 * 1000, // 5 minutes
  });
}

/**
 * EN: Mutation with cache invalidation
 * VI: Mutation với invalidation cache
 */
export function useCreateChat() {
  const queryClient = useQueryClient();
  
  return useMutation({
    mutationFn: createChat,
    onSuccess: () => {
      // Invalidate all chat lists
      queryClient.invalidateQueries({ queryKey: chatKeys.lists() });
    },
  });
}
```

### Optimistic Updates

```typescript
/**
 * EN: Optimistic update for better UX
 * VI: Optimistic update cho UX tốt hơn
 */
export function useSendMessage() {
  const queryClient = useQueryClient();
  
  return useMutation({
    mutationFn: sendMessage,
    onMutate: async (newMessage) => {
      // Cancel outgoing refetches
      await queryClient.cancelQueries({ queryKey: chatKeys.detail(newMessage.chatId) });
      
      // Snapshot previous value
      const previous = queryClient.getQueryData(chatKeys.detail(newMessage.chatId));
      
      // Optimistically update
      queryClient.setQueryData(chatKeys.detail(newMessage.chatId), (old: Chat) => ({
        ...old,
        messages: [...old.messages, { ...newMessage, status: 'sending' }],
      }));
      
      return { previous };
    },
    onError: (err, newMessage, context) => {
      // Rollback on error
      queryClient.setQueryData(
        chatKeys.detail(newMessage.chatId),
        context?.previous
      );
    },
    onSettled: (_, __, { chatId }) => {
      // Always refetch after error or success
      queryClient.invalidateQueries({ queryKey: chatKeys.detail(chatId) });
    },
  });
}
```

### Provider Setup

```tsx
// providers/query-provider.tsx
'use client';

import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
import { useState } from 'react';

/**
 * EN: Query provider with client-side only instantiation
 * VI: Query provider với khởi tạo chỉ phía client
 */
export function QueryProvider({ children }: { children: React.ReactNode }) {
  const [queryClient] = useState(
    () =>
      new QueryClient({
        defaultOptions: {
          queries: {
            staleTime: 60 * 1000, // 1 minute
            refetchOnWindowFocus: false,
          },
        },
      })
  );

  return (
    <QueryClientProvider client={queryClient}>
      {children}
      <ReactQueryDevtools initialIsOpen={false} />
    </QueryClientProvider>
  );
}
```

## Testing Patterns / Mẫu Testing

### Testing Zustand Stores

```typescript
// stores/__tests__/auth-store.test.ts
import { describe, it, expect, beforeEach } from 'vitest';
import { useAuthStore } from '../auth-store';

describe('AuthStore', () => {
  beforeEach(() => {
    // Reset store before each test
    useAuthStore.setState({ user: null, isAuthenticated: false });
  });

  it('should login user', () => {
    const user = { id: '1', name: 'Test' };
    useAuthStore.getState().login(user);
    
    expect(useAuthStore.getState().user).toEqual(user);
    expect(useAuthStore.getState().isAuthenticated).toBe(true);
  });

  it('should logout user', () => {
    useAuthStore.setState({ user: { id: '1' }, isAuthenticated: true });
    useAuthStore.getState().logout();
    
    expect(useAuthStore.getState().user).toBeNull();
    expect(useAuthStore.getState().isAuthenticated).toBe(false);
  });
});
```

## Common Mistakes / Lỗi Thường Gặp

### 1. Subscribing to Entire Store

```typescript
// ❌ BAD: Component re-renders on any store change
const store = useAuthStore();

// ✅ GOOD: Select only needed data
const userName = useAuthStore((s) => s.user?.name);
```

### 2. Missing Query Key Invalidation

```typescript
// ❌ BAD: Cache becomes stale after mutation
useMutation({ mutationFn: updateUser });

// ✅ GOOD: Invalidate related queries
useMutation({
  mutationFn: updateUser,
  onSuccess: () => queryClient.invalidateQueries({ queryKey: userKeys.all }),
});
```

### 3. Creating QueryClient in Component Body

```tsx
// ❌ BAD: New client on every render
function App() {
  const queryClient = new QueryClient(); // Recreated every render!
}

// ✅ GOOD: Use useState or module scope
function App() {
  const [queryClient] = useState(() => new QueryClient());
}
```

## Quick Reference / Tham Chiếu Nhanh

| Pattern | When to Use |
|---------|-------------|
| `create()` | Basic Zustand store |
| `devtools()` | Enable Redux DevTools |
| `persist()` | Persist to localStorage |
| `useQuery()` | Fetch & cache server data |
| `useMutation()` | Create/Update/Delete operations |
| `queryClient.invalidateQueries()` | Refresh cache after mutation |
| `queryClient.setQueryData()` | Optimistic updates |

## Resources / Tài Nguyên

- [React Enterprise Architect](../react-enterprise-architect/SKILL.md) - Architecture patterns
- [React Testing Patterns](../react-testing-patterns/SKILL.md) - Testing strategies
- [Zustand Docs](https://zustand-demo.pmnd.rs/)
- [TanStack Query Docs](https://tanstack.com/query/latest)
