🎯 State Management Deep Dive

Master the art of managing data flow in modern React + WordPress applications

The State Management Landscape: Choosing Your Weapon

State management in React is like choosing transportation for a journey. Walking (local state) works for short distances, a bike (Context API) for medium trips, a car (Zustand) for flexibility, or a train (Redux) for long, complex journeys with many passengers (data).

Solution Best For Learning Curve Bundle Size DevTools
useState Component-level state ⭐ Easy 0KB (built-in) React DevTools
Context API Theme, Auth, Settings ⭐⭐ Moderate 0KB (built-in) React DevTools
Zustand App-wide state ⭐⭐ Moderate ~8KB Zustand DevTools
React Query Server state, API cache ⭐⭐⭐ Advanced ~40KB React Query DevTools
Redux Toolkit Complex apps, time-travel ⭐⭐⭐⭐ Complex ~45KB Redux DevTools

Context API: The Broadcasting Tower

Context is like a radio broadcasting tower. Any component can tune in (useContext) to receive the signal (state), avoiding the telephone game of passing props through every component.

graph TD subgraph "Context Provider Tower" Provider[ThemeProvider
UserProvider
CartProvider] end subgraph "Component Tree" App[App] Header[Header] Main[Main] Footer[Footer] Nav[Navigation] Profile[Profile] Cart[CartIcon] Products[ProductList] Settings[Settings] end Provider -.->|broadcasts| Header Provider -.->|broadcasts| Profile Provider -.->|broadcasts| Cart Provider -.->|broadcasts| Settings Provider -.->|broadcasts| Products App --> Header App --> Main App --> Footer Header --> Nav Nav --> Profile Nav --> Cart Main --> Products Footer --> Settings style Provider fill:#3498db,stroke:#333,stroke-width:3px style Profile fill:#2ecc71 style Cart fill:#2ecc71 style Settings fill:#2ecc71 style Products fill:#2ecc71

Pattern 1: Multi-Provider Pattern

Organizing Multiple Contexts

// providers/AppProviders.js
import { ThemeProvider } from './ThemeContext';
import { AuthProvider } from './AuthContext';
import { WordPressProvider } from './WordPressContext';

export const AppProviders = ({ children }) => {
  return (
    <ThemeProvider>
      <AuthProvider>
        <WordPressProvider>
          {children}
        </WordPressProvider>
      </AuthProvider>
    </ThemeProvider>
  );
};

// App.js
import { AppProviders } from './providers/AppProviders';

function App() {
  return (
    <AppProviders>
      <Router>
        <YourAppContent />
      </Router>
    </AppProviders>
  );
}

Pattern 2: WordPress Data Context

Managing WordPress Content with Context

// contexts/WordPressContext.js
import React, { createContext, useContext, useReducer, useEffect } from 'react';
import { WordPressAPI } from '../services/api';

const WordPressContext = createContext();

// Action types
const ACTIONS = {
  FETCH_POSTS_START: 'FETCH_POSTS_START',
  FETCH_POSTS_SUCCESS: 'FETCH_POSTS_SUCCESS',
  FETCH_POSTS_ERROR: 'FETCH_POSTS_ERROR',
  SET_CURRENT_POST: 'SET_CURRENT_POST',
  UPDATE_CACHE: 'UPDATE_CACHE'
};

// Reducer
const wordpressReducer = (state, action) => {
  switch (action.type) {
    case ACTIONS.FETCH_POSTS_START:
      return { ...state, loading: true, error: null };
    
    case ACTIONS.FETCH_POSTS_SUCCESS:
      return {
        ...state,
        posts: action.payload,
        loading: false,
        lastFetch: Date.now()
      };
    
    case ACTIONS.FETCH_POSTS_ERROR:
      return { ...state, loading: false, error: action.payload };
    
    case ACTIONS.SET_CURRENT_POST:
      return { ...state, currentPost: action.payload };
    
    default:
      return state;
  }
};

// Provider component
export const WordPressProvider = ({ children }) => {
  const [state, dispatch] = useReducer(wordpressReducer, {
    posts: [],
    currentPost: null,
    loading: false,
    error: null,
    lastFetch: null
  });

  // Fetch posts with caching
  const fetchPosts = async (force = false) => {
    // Check cache validity (5 minutes)
    const cacheValid = state.lastFetch && 
      (Date.now() - state.lastFetch) < 5 * 60 * 1000;
    
    if (!force && cacheValid) return;

    dispatch({ type: ACTIONS.FETCH_POSTS_START });
    
    try {
      const posts = await WordPressAPI.getPosts();
      dispatch({ type: ACTIONS.FETCH_POSTS_SUCCESS, payload: posts });
    } catch (error) {
      dispatch({ type: ACTIONS.FETCH_POSTS_ERROR, payload: error.message });
    }
  };

  const value = {
    ...state,
    fetchPosts,
    setCurrentPost: (post) => 
      dispatch({ type: ACTIONS.SET_CURRENT_POST, payload: post })
  };

  return (
    <WordPressContext.Provider value={value}>
      {children}
    </WordPressContext.Provider>
  );
};

// Custom hook
export const useWordPress = () => {
  const context = useContext(WordPressContext);
  if (!context) {
    throw new Error('useWordPress must be used within WordPressProvider');
  }
  return context;
};
💡 Pro Tip: Split contexts by update frequency. High-frequency updates (like form inputs) should be in separate contexts from low-frequency updates (like user authentication) to prevent unnecessary re-renders.

Redux with WordPress: The Command Center

Redux is like NASA's mission control - every action is logged, every state change is tracked, and you can literally time-travel through your application's history. Perfect for complex WordPress applications with multiple data sources.

Redux Toolkit with WordPress

Modern Redux Setup for WordPress

// store/slices/wordpressSlice.js
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';
import { WordPressAPI } from '../../services/api';

// Async thunks for WordPress API
export const fetchPosts = createAsyncThunk(
  'wordpress/fetchPosts',
  async ({ page = 1, perPage = 10 }, { rejectWithValue }) => {
    try {
      const response = await WordPressAPI.getPosts({ page, perPage });
      return response;
    } catch (error) {
      return rejectWithValue(error.message);
    }
  }
);

export const fetchPostById = createAsyncThunk(
  'wordpress/fetchPostById',
  async (id) => {
    const response = await WordPressAPI.getPost(id);
    return response;
  }
);

export const createComment = createAsyncThunk(
  'wordpress/createComment',
  async ({ postId, content, authorName, authorEmail }) => {
    const response = await WordPressAPI.createComment({
      post: postId,
      content,
      author_name: authorName,
      author_email: authorEmail
    });
    return response;
  }
);

// Slice
const wordpressSlice = createSlice({
  name: 'wordpress',
  initialState: {
    posts: {
      items: [],
      currentPage: 1,
      totalPages: 0,
      total: 0,
      loading: false,
      error: null
    },
    currentPost: {
      data: null,
      comments: [],
      loading: false,
      error: null
    },
    categories: [],
    tags: []
  },
  reducers: {
    clearCurrentPost: (state) => {
      state.currentPost = { data: null, comments: [], loading: false, error: null };
    },
    updatePostInCache: (state, action) => {
      const index = state.posts.items.findIndex(p => p.id === action.payload.id);
      if (index !== -1) {
        state.posts.items[index] = action.payload;
      }
    }
  },
  extraReducers: (builder) => {
    builder
      // Fetch posts
      .addCase(fetchPosts.pending, (state) => {
        state.posts.loading = true;
        state.posts.error = null;
      })
      .addCase(fetchPosts.fulfilled, (state, action) => {
        state.posts.loading = false;
        state.posts.items = action.payload.posts;
        state.posts.totalPages = action.payload.totalPages;
        state.posts.total = action.payload.total;
      })
      .addCase(fetchPosts.rejected, (state, action) => {
        state.posts.loading = false;
        state.posts.error = action.payload;
      })
      
      // Fetch single post
      .addCase(fetchPostById.pending, (state) => {
        state.currentPost.loading = true;
      })
      .addCase(fetchPostById.fulfilled, (state, action) => {
        state.currentPost.loading = false;
        state.currentPost.data = action.payload;
      })
      
      // Create comment
      .addCase(createComment.fulfilled, (state, action) => {
        state.currentPost.comments.push(action.payload);
      });
  }
});

export const { clearCurrentPost, updatePostInCache } = wordpressSlice.actions;
export default wordpressSlice.reducer;

// Selectors
export const selectAllPosts = (state) => state.wordpress.posts.items;
export const selectCurrentPost = (state) => state.wordpress.currentPost.data;
export const selectPostsLoading = (state) => state.wordpress.posts.loading;
⚠️ Warning: Redux can be overkill for simple WordPress blogs. Use it when you need features like undo/redo, complex state synchronization, or time-travel debugging.

Zustand: The Lightweight Champion

Zustand is like a smart filing cabinet - simple to use but surprisingly powerful. It combines the simplicity of useState with the power of global state management, perfect for WordPress apps that need more than Context but less than Redux.

graph LR subgraph "Zustand Store" Store[Single Store
posts, user, theme] end subgraph "Components" A[BlogList] B[UserProfile] C[ThemeToggle] D[Comments] end Store -.->|subscribe| A Store -.->|subscribe| B Store -.->|subscribe| C Store -.->|subscribe| D A -->|update| Store B -->|update| Store C -->|update| Store D -->|update| Store style Store fill:#9b59b6,stroke:#333,stroke-width:3px

Zustand WordPress Store

Complete Zustand Setup for WordPress

// stores/useWordPressStore.js
import { create } from 'zustand';
import { devtools, persist } from 'zustand/middleware';
import { WordPressAPI } from '../services/api';

const useWordPressStore = create(
  devtools(
    persist(
      (set, get) => ({
        // State
        posts: [],
        currentPost: null,
        categories: [],
        searchResults: [],
        filters: {
          category: null,
          tag: null,
          search: ''
        },
        ui: {
          loading: false,
          error: null,
          theme: 'light'
        },
        
        // Actions
        fetchPosts: async (options = {}) => {
          set({ ui: { ...get().ui, loading: true, error: null } });
          
          try {
            const posts = await WordPressAPI.getPosts(options);
            set({ 
              posts,
              ui: { ...get().ui, loading: false }
            });
            return posts;
          } catch (error) {
            set({ 
              ui: { ...get().ui, loading: false, error: error.message }
            });
            throw error;
          }
        },
        
        fetchPostBySlug: async (slug) => {
          // Check if already in cache
          const cached = get().posts.find(p => p.slug === slug);
          if (cached && !isStale(cached)) {
            set({ currentPost: cached });
            return cached;
          }
          
          set({ ui: { ...get().ui, loading: true } });
          
          try {
            const post = await WordPressAPI.getPostBySlug(slug);
            set({ 
              currentPost: post,
              ui: { ...get().ui, loading: false }
            });
            
            // Update cache
            const posts = get().posts;
            const index = posts.findIndex(p => p.id === post.id);
            if (index !== -1) {
              posts[index] = post;
              set({ posts: [...posts] });
            }
            
            return post;
          } catch (error) {
            set({ 
              ui: { ...get().ui, loading: false, error: error.message }
            });
            throw error;
          }
        },
        
        searchPosts: async (query) => {
          set({ 
            filters: { ...get().filters, search: query },
            ui: { ...get().ui, loading: true }
          });
          
          try {
            const results = await WordPressAPI.searchPosts(query);
            set({ 
              searchResults: results,
              ui: { ...get().ui, loading: false }
            });
            return results;
          } catch (error) {
            set({ 
              ui: { ...get().ui, loading: false, error: error.message }
            });
            throw error;
          }
        },
        
        toggleTheme: () => {
          const newTheme = get().ui.theme === 'light' ? 'dark' : 'light';
          set({ ui: { ...get().ui, theme: newTheme } });
          document.documentElement.classList.toggle('dark');
        },
        
        clearError: () => {
          set({ ui: { ...get().ui, error: null } });
        },
        
        // Computed values
        get filteredPosts() {
          const { posts, filters } = get();
          let filtered = posts;
          
          if (filters.category) {
            filtered = filtered.filter(p => 
              p.categories.includes(filters.category)
            );
          }
          
          if (filters.tag) {
            filtered = filtered.filter(p => 
              p.tags.includes(filters.tag)
            );
          }
          
          if (filters.search) {
            const query = filters.search.toLowerCase();
            filtered = filtered.filter(p => 
              p.title.rendered.toLowerCase().includes(query) ||
              p.excerpt.rendered.toLowerCase().includes(query)
            );
          }
          
          return filtered;
        }
      }),
      {
        name: 'wordpress-storage',
        partialize: (state) => ({ 
          posts: state.posts,
          categories: state.categories,
          ui: { theme: state.ui.theme }
        })
      }
    )
  )
);

// Utility function
function isStale(post, maxAge = 5 * 60 * 1000) {
  return Date.now() - new Date(post.fetchedAt).getTime() > maxAge;
}

// Usage in components
export default useWordPressStore;

// Component example
function BlogList() {
  const { posts, fetchPosts, ui } = useWordPressStore();
  
  useEffect(() => {
    if (posts.length === 0) {
      fetchPosts();
    }
  }, []);
  
  if (ui.loading) return ;
  if (ui.error) return ;
  
  return (
    <div>
      {posts.map(post => (
        <PostCard key={post.id} post={post} />
      ))}
    </div>
  );
}
💡 Pro Tip: Zustand's persist middleware automatically saves state to localStorage, perfect for offline-first WordPress apps!

React Query: The Server State Specialist

React Query (TanStack Query) is like having a smart assistant who manages all your server communications. It knows when to fetch, when to use cache, when to refetch, and handles all the loading states automatically.

React Query with WordPress

Advanced React Query Setup

// hooks/useWordPressQuery.js
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { WordPressAPI } from '../services/api';

// Query keys factory
const queryKeys = {
  all: ['wordpress'],
  posts: () => [...queryKeys.all, 'posts'],
  post: (id) => [...queryKeys.posts(), id],
  postBySlug: (slug) => [...queryKeys.posts(), 'slug', slug],
  comments: (postId) => [...queryKeys.all, 'comments', postId],
  categories: () => [...queryKeys.all, 'categories'],
  search: (query) => [...queryKeys.all, 'search', query]
};

// Hooks
export function usePosts(options = {}) {
  return useQuery({
    queryKey: queryKeys.posts(),
    queryFn: () => WordPressAPI.getPosts(options),
    staleTime: 5 * 60 * 1000, // 5 minutes
    cacheTime: 10 * 60 * 1000, // 10 minutes
    refetchOnWindowFocus: false,
    refetchOnMount: 'always'
  });
}

export function usePost(slug) {
  return useQuery({
    queryKey: queryKeys.postBySlug(slug),
    queryFn: () => WordPressAPI.getPostBySlug(slug),
    staleTime: 10 * 60 * 1000,
    enabled: !!slug // Only run if slug exists
  });
}

export function useInfinitePosts() {
  return useInfiniteQuery({
    queryKey: queryKeys.posts(),
    queryFn: ({ pageParam = 1 }) => 
      WordPressAPI.getPosts({ page: pageParam, perPage: 10 }),
    getNextPageParam: (lastPage, pages) => {
      return lastPage.hasMore ? pages.length + 1 : undefined;
    },
    staleTime: 5 * 60 * 1000
  });
}

export function useCreateComment() {
  const queryClient = useQueryClient();
  
  return useMutation({
    mutationFn: (commentData) => WordPressAPI.createComment(commentData),
    onSuccess: (newComment, variables) => {
      // Update cache optimistically
      queryClient.setQueryData(
        queryKeys.comments(variables.postId),
        (old) => [...(old || []), newComment]
      );
      
      // Invalidate post to refetch with new comment count
      queryClient.invalidateQueries(queryKeys.post(variables.postId));
    },
    onError: (error, variables, context) => {
      // Rollback optimistic update on error
      if (context?.previousComments) {
        queryClient.setQueryData(
          queryKeys.comments(variables.postId),
          context.previousComments
        );
      }
    }
  });
}

// Prefetching for better UX
export function usePrefetchPost() {
  const queryClient = useQueryClient();
  
  return (slug) => {
    queryClient.prefetchQuery({
      queryKey: queryKeys.postBySlug(slug),
      queryFn: () => WordPressAPI.getPostBySlug(slug),
      staleTime: 10 * 60 * 1000
    });
  };
}

// Component usage example
function BlogPost({ slug }) {
  const { data: post, isLoading, error } = usePost(slug);
  const { mutate: createComment } = useCreateComment();
  
  if (isLoading) return ;
  if (error) return ;
  
  return (
    <article>
      <h1>{post.title.rendered}</h1>
      <div dangerouslySetInnerHTML={{ __html: post.content.rendered }} />
      <CommentForm 
        onSubmit={(data) => createComment({ ...data, postId: post.id })}
      />
    </article>
  );
}

// App.js setup
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';

const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      retry: 2,
      retryDelay: (attemptIndex) => Math.min(1000 * 2 ** attemptIndex, 30000),
      refetchOnWindowFocus: false,
      staleTime: 5 * 60 * 1000
    }
  }
});

function App() {
  return (
    <QueryClientProvider client={queryClient}>
      <YourApp />
      <ReactQueryDevtools initialIsOpen={false} />
    </QueryClientProvider>
  );
}

State Persistence Strategies

State persistence is like having a photographic memory for your app. Even when users close the browser, come back tomorrow, or lose internet connection, your app remembers exactly where they left off.

graph TD subgraph "Persistence Layers" Memory[Memory
Fastest, Temporary] SessionStorage[Session Storage
Tab lifetime] LocalStorage[Local Storage
Permanent] IndexedDB[IndexedDB
Large data] Server[Server/Cloud
Cross-device] end Memory --> SessionStorage SessionStorage --> LocalStorage LocalStorage --> IndexedDB IndexedDB --> Server style Memory fill:#2ecc71 style SessionStorage fill:#3498db style LocalStorage fill:#9b59b6 style IndexedDB fill:#f39c12 style Server fill:#e74c3c

Comprehensive Persistence Solution

Multi-Layer Persistence Strategy

// utils/persistence.js
class PersistenceManager {
  constructor() {
    this.storage = this.detectStorage();
    this.prefix = 'wp_app_';
  }
  
  detectStorage() {
    // Check available storage options
    if (typeof window !== 'undefined') {
      if ('indexedDB' in window) {
        return new IndexedDBAdapter();
      }
      if ('localStorage' in window) {
        return new LocalStorageAdapter();
      }
    }
    return new MemoryAdapter();
  }
  
  async save(key, data, options = {}) {
    const fullKey = this.prefix + key;
    const dataToSave = {
      data,
      timestamp: Date.now(),
      version: options.version || 1,
      ttl: options.ttl || null
    };
    
    try {
      if (options.encrypt) {
        dataToSave.data = await this.encrypt(data);
      }
      
      await this.storage.setItem(fullKey, JSON.stringify(dataToSave));
      
      // Sync to server if enabled
      if (options.sync) {
        this.syncToServer(key, data);
      }
    } catch (error) {
      console.error('Persistence error:', error);
      // Fallback to memory
      this.fallbackMemory[fullKey] = dataToSave;
    }
  }
  
  async load(key, options = {}) {
    const fullKey = this.prefix + key;
    
    try {
      const stored = await this.storage.getItem(fullKey);
      if (!stored) return null;
      
      const parsed = JSON.parse(stored);
      
      // Check TTL
      if (parsed.ttl && Date.now() - parsed.timestamp > parsed.ttl) {
        await this.storage.removeItem(fullKey);
        return null;
      }
      
      // Check version
      if (options.version && parsed.version !== options.version) {
        await this.migrate(key, parsed, options.version);
      }
      
      if (options.decrypt && parsed.encrypted) {
        parsed.data = await this.decrypt(parsed.data);
      }
      
      return parsed.data;
    } catch (error) {
      console.error('Load error:', error);
      return this.fallbackMemory[fullKey]?.data || null;
    }
  }
  
  async clear(pattern) {
    const keys = await this.storage.keys();
    const toDelete = keys.filter(key => 
      key.startsWith(this.prefix) && 
      (!pattern || key.includes(pattern))
    );
    
    await Promise.all(toDelete.map(key => 
      this.storage.removeItem(key)
    ));
  }
  
  // IndexedDB Adapter for large data
  class IndexedDBAdapter {
    constructor() {
      this.dbName = 'WordPressAppDB';
      this.storeName = 'state';
      this.db = null;
    }
    
    async init() {
      return new Promise((resolve, reject) => {
        const request = indexedDB.open(this.dbName, 1);
        
        request.onerror = () => reject(request.error);
        request.onsuccess = () => {
          this.db = request.result;
          resolve();
        };
        
        request.onupgradeneeded = (event) => {
          const db = event.target.result;
          if (!db.objectStoreNames.contains(this.storeName)) {
            db.createObjectStore(this.storeName);
          }
        };
      });
    }
    
    async setItem(key, value) {
      if (!this.db) await this.init();
      
      return new Promise((resolve, reject) => {
        const transaction = this.db.transaction([this.storeName], 'readwrite');
        const store = transaction.objectStore(this.storeName);
        const request = store.put(value, key);
        
        request.onsuccess = () => resolve();
        request.onerror = () => reject(request.error);
      });
    }
    
    async getItem(key) {
      if (!this.db) await this.init();
      
      return new Promise((resolve, reject) => {
        const transaction = this.db.transaction([this.storeName], 'readonly');
        const store = transaction.objectStore(this.storeName);
        const request = store.get(key);
        
        request.onsuccess = () => resolve(request.result);
        request.onerror = () => reject(request.error);
      });
    }
  }
}

// Custom hook for persisted state
export function usePersistedState(key, initialValue, options = {}) {
  const [state, setState] = useState(initialValue);
  const [isLoading, setIsLoading] = useState(true);
  const persistence = useRef(new PersistenceManager());
  
  // Load persisted state
  useEffect(() => {
    persistence.current.load(key, options).then(saved => {
      if (saved !== null) {
        setState(saved);
      }
      setIsLoading(false);
    });
  }, [key]);
  
  // Save state changes
  const setPersistedState = useCallback((value) => {
    setState(value);
    persistence.current.save(key, value, options);
  }, [key, options]);
  
  return [state, setPersistedState, isLoading];
}

// Usage example
function BlogReader() {
  const [readPosts, setReadPosts] = usePersistedState('read_posts', [], {
    ttl: 30 * 24 * 60 * 60 * 1000, // 30 days
    sync: true
  });
  
  const [preferences, setPreferences] = usePersistedState('user_prefs', {
    theme: 'light',
    fontSize: 16,
    notifications: true
  }, {
    encrypt: true,
    version: 2
  });
  
  // Component logic...
}
💡 Pro Tip: Use different storage strategies for different data types:
  • Session Storage: Form drafts, temporary UI state
  • Local Storage: User preferences, read articles
  • IndexedDB: Offline cache, large datasets
  • Server: Cross-device sync, backups

Choosing the Right Solution

Selecting the right state management solution is like choosing the right vehicle for a journey. Here's your decision matrix:

Quick Decision Guide

  • useState: Form inputs, toggles, simple counters
  • Context API: Theme, authentication, user preferences
  • Zustand: Shopping cart, user sessions, app-wide UI state
  • React Query: Blog posts, comments, any WordPress data
  • Redux: Complex e-commerce, real-time collaboration, undo/redo

Real-World Example: Complete Blog State Architecture

Let's combine everything into a production-ready WordPress blog application using the best of each solution:

Hybrid State Management Architecture

// Complete state architecture for a WordPress blog

// 1. React Query for server state (posts, comments)
// hooks/queries.js
export const useBlogPosts = () => useQuery({
  queryKey: ['posts'],
  queryFn: WordPressAPI.getPosts
});

// 2. Zustand for client state (UI, preferences)
// stores/uiStore.js
export const useUIStore = create(persist(
  (set) => ({
    theme: 'light',
    sidebarOpen: false,
    toggleTheme: () => set(state => ({ 
      theme: state.theme === 'light' ? 'dark' : 'light' 
    })),
    toggleSidebar: () => set(state => ({ 
      sidebarOpen: !state.sidebarOpen 
    }))
  }),
  { name: 'ui-preferences' }
));

// 3. Context for authentication
// contexts/AuthContext.js
export const AuthProvider = ({ children }) => {
  const [user, setUser] = useState(null);
  
  const login = async (credentials) => {
    const user = await WordPressAPI.login(credentials);
    setUser(user);
    localStorage.setItem('wp_token', user.token);
  };
  
  return (
    <AuthContext.Provider value={{ user, login }}>
      {children}
    </AuthContext.Provider>
  );
};

// 4. Local state for forms
// components/CommentForm.js
function CommentForm({ postId }) {
  const [comment, setComment] = useState('');
  const mutation = useCreateComment();
  
  const handleSubmit = (e) => {
    e.preventDefault();
    mutation.mutate({ postId, content: comment });
    setComment('');
  };
  
  return (
    <form onSubmit={handleSubmit}>
      <textarea 
        value={comment}
        onChange={(e) => setComment(e.target.value)}
      />
      <button type="submit">Post Comment</button>
    </form>
  );
}

// 5. Main App with all providers
function App() {
  return (
    <QueryClientProvider client={queryClient}>
      <AuthProvider>
        <BlogApp />
      </AuthProvider>
    </QueryClientProvider>
  );
}