state-management-advisor

State Management Advisor

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "state-management-advisor" with this command: npx skills add armanzeroeight/fastagent-plugins/armanzeroeight-fastagent-plugins-state-management-advisor

State Management Advisor

Choose and implement the right state management solution for React applications.

Quick Start

Use local state by default, Context for simple global state, Zustand for complex client state, and TanStack Query for server state.

Instructions

Decision Tree

Start here:

  • Is it server data (from API)? → Use TanStack Query

  • Is it used in one component only? → Use useState

  • Is it simple global state (theme, auth)? → Use Context

  • Is it complex client state? → Use Zustand or Redux Toolkit

Local State (useState)

For component-specific state.

Basic usage:

function Counter() { const [count, setCount] = useState(0);

return ( <button onClick={() => setCount(count + 1)}> Count: {count} </button> ); }

With objects:

function Form() { const [formData, setFormData] = useState({ name: '', email: '' });

const handleChange = (field: string, value: string) => { setFormData(prev => ({ ...prev, [field]: value })); };

return ( <form> <input value={formData.name} onChange={e => handleChange('name', e.target.value)} /> </form> ); }

React Context

For simple global state shared across components.

Setup:

interface AuthContextValue { user: User | null; login: (credentials: Credentials) => 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 login = async (credentials: Credentials) => { const user = await api.login(credentials); setUser(user); };

const logout = () => { setUser(null); };

return ( <AuthContext.Provider value={{ user, login, logout }}> {children} </AuthContext.Provider> ); }

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

Usage:

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

function Profile() { const { user, logout } = useAuth(); return <div>{user?.name} <button onClick={logout}>Logout</button></div>; }

Optimization - Split contexts:

// Separate frequently changing data const ThemeContext = createContext<Theme>(null); const ThemeUpdateContext = createContext<(theme: Theme) => void>(null);

function ThemeProvider({ children }) { const [theme, setTheme] = useState('light');

return ( <ThemeContext.Provider value={theme}> <ThemeUpdateContext.Provider value={setTheme}> {children} </ThemeUpdateContext.Provider> </ThemeContext.Provider> ); }

Zustand

Lightweight global state with minimal boilerplate.

Installation:

npm install zustand

Basic store:

import { create } from 'zustand';

interface CounterStore { count: number; increment: () => void; decrement: () => void; }

const useCounterStore = create<CounterStore>((set) => ({ count: 0, increment: () => set((state) => ({ count: state.count + 1 })), decrement: () => set((state) => ({ count: state.count - 1 })), }));

// Usage function Counter() { const count = useCounterStore((state) => state.count); const increment = useCounterStore((state) => state.increment);

return <button onClick={increment}>Count: {count}</button>; }

With async actions:

interface UserStore { users: User[]; loading: boolean; fetchUsers: () => Promise<void>; }

const useUserStore = create<UserStore>((set) => ({ users: [], loading: false, fetchUsers: async () => { set({ loading: true }); const users = await api.fetchUsers(); set({ users, loading: false }); }, }));

With middleware:

import { persist } from 'zustand/middleware';

const useStore = create( persist( (set) => ({ token: null, setToken: (token) => set({ token }), }), { name: 'auth-storage', } ) );

Redux Toolkit

For complex state with many interactions.

Installation:

npm install @reduxjs/toolkit react-redux

Setup store:

import { configureStore, createSlice } from '@reduxjs/toolkit';

const counterSlice = createSlice({ name: 'counter', initialState: { value: 0 }, reducers: { increment: (state) => { state.value += 1; }, decrement: (state) => { state.value -= 1; }, }, });

export const { increment, decrement } = counterSlice.actions;

export const store = configureStore({ reducer: { counter: counterSlice.reducer, }, });

export type RootState = ReturnType<typeof store.getState>; export type AppDispatch = typeof store.dispatch;

Provider:

import { Provider } from 'react-redux';

function App() { return ( <Provider store={store}> <Router /> </Provider> ); }

Usage:

import { useSelector, useDispatch } from 'react-redux';

function Counter() { const count = useSelector((state: RootState) => state.counter.value); const dispatch = useDispatch();

return ( <button onClick={() => dispatch(increment())}> Count: {count} </button> ); }

Async with createAsyncThunk:

import { createAsyncThunk } from '@reduxjs/toolkit';

export const fetchUsers = createAsyncThunk( 'users/fetch', async () => { const response = await api.fetchUsers(); return response.data; } );

const usersSlice = createSlice({ name: 'users', initialState: { data: [], loading: false }, reducers: {}, extraReducers: (builder) => { builder .addCase(fetchUsers.pending, (state) => { state.loading = true; }) .addCase(fetchUsers.fulfilled, (state, action) => { state.data = action.payload; state.loading = false; }); }, });

TanStack Query (React Query)

For server state management.

Installation:

npm install @tanstack/react-query

Setup:

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

const queryClient = new QueryClient();

function App() { return ( <QueryClientProvider client={queryClient}> <Router /> </QueryClientProvider> ); }

Fetching data:

import { useQuery } from '@tanstack/react-query';

function Users() { const { data, isLoading, error } = useQuery({ queryKey: ['users'], queryFn: fetchUsers, });

if (isLoading) return <Spinner />; if (error) return <Error message={error.message} />;

return <UserList users={data} />; }

Mutations:

import { useMutation, useQueryClient } from '@tanstack/react-query';

function CreateUser() { const queryClient = useQueryClient();

const mutation = useMutation({ mutationFn: createUser, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['users'] }); }, });

return ( <button onClick={() => mutation.mutate({ name: 'John' })}> Create User </button> ); }

With parameters:

function User({ id }: { id: string }) { const { data } = useQuery({ queryKey: ['user', id], queryFn: () => fetchUser(id), });

return <div>{data?.name}</div>; }

Optimistic updates:

const mutation = useMutation({ mutationFn: updateUser, onMutate: async (newUser) => { await queryClient.cancelQueries({ queryKey: ['users'] }); const previousUsers = queryClient.getQueryData(['users']);

queryClient.setQueryData(['users'], (old) => [...old, newUser]);

return { previousUsers };

}, onError: (err, newUser, context) => { queryClient.setQueryData(['users'], context.previousUsers); }, onSettled: () => { queryClient.invalidateQueries({ queryKey: ['users'] }); }, });

Jotai

Atomic state management.

Installation:

npm install jotai

Basic atoms:

import { atom, useAtom } from 'jotai';

const countAtom = atom(0);

function Counter() { const [count, setCount] = useAtom(countAtom);

return ( <button onClick={() => setCount(count + 1)}> Count: {count} </button> ); }

Derived atoms:

const countAtom = atom(0); const doubleCountAtom = atom((get) => get(countAtom) * 2);

function Display() { const [doubleCount] = useAtom(doubleCountAtom); return <div>Double: {doubleCount}</div>; }

Async atoms:

const usersAtom = atom(async () => { const response = await fetch('/api/users'); return response.json(); });

function Users() { const [users] = useAtom(usersAtom); return <UserList users={users} />; }

Comparison Matrix

Solution Best For Pros Cons

useState Local state Simple, built-in Limited to component

Context Simple global state Built-in, no deps Can cause re-renders

Zustand Complex client state Minimal, fast Smaller ecosystem

Redux Toolkit Large apps, teams Powerful, DevTools Verbose, learning curve

TanStack Query Server state Caching, sync Not for client state

Jotai Atomic state Fine-grained, modern Smaller ecosystem

Common Patterns

Combining Solutions

TanStack Query + Zustand:

// Server state with TanStack Query const { data: users } = useQuery(['users'], fetchUsers);

// Client state with Zustand const selectedUserId = useStore((state) => state.selectedUserId);

Context + TanStack Query:

function AuthProvider({ children }) { const { data: user } = useQuery(['me'], fetchCurrentUser);

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

Form State

Controlled with useState:

function Form() { const [values, setValues] = useState({ name: '', email: '' });

const handleSubmit = (e) => { e.preventDefault(); api.submit(values); };

return ( <form onSubmit={handleSubmit}> <input value={values.name} onChange={e => setValues(prev => ({ ...prev, name: e.target.value }))} /> </form> ); }

With form library:

import { useForm } from 'react-hook-form';

function Form() { const { register, handleSubmit } = useForm();

const onSubmit = (data) => api.submit(data);

return ( <form onSubmit={handleSubmit(onSubmit)}> <input {...register('name')} /> </form> ); }

Troubleshooting

Context causing re-renders:

  • Split into multiple contexts

  • Use useMemo for context value

  • Consider Zustand instead

Stale data in TanStack Query:

  • Adjust staleTime and cacheTime

  • Use refetchInterval for polling

  • Invalidate queries after mutations

Redux boilerplate:

  • Use Redux Toolkit (not legacy Redux)

  • Use createSlice for reducers

  • Use createAsyncThunk for async

State updates not reflecting:

  • Check if mutating state directly

  • Use functional updates

  • Verify dependencies in useEffect

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

Automation

gcp-cost-optimizer

No summary provided by upstream source.

Repository SourceNeeds Review
Automation

schema-designer

No summary provided by upstream source.

Repository SourceNeeds Review
Automation

api-documentation-generator

No summary provided by upstream source.

Repository SourceNeeds Review
Automation

aws-cost-optimizer

No summary provided by upstream source.

Repository SourceNeeds Review