Modern State Management Implementation

1. Redux Toolkit RTK Query Setup

Feature Syntax Description Use Case
configureStore configureStore({ reducer: {} }) Simplifies store setup with good defaults, DevTools Store initialization
createSlice createSlice({ name, initialState, reducers }) Generates action creators and reducers automatically State domain logic
createAsyncThunk createAsyncThunk('type', async () => {}) Handles async logic with pending/fulfilled/rejected actions API calls, side effects
createApi createApi({ baseQuery, endpoints }) RTK Query API service with auto-generated hooks Data fetching & caching
useSelector const data = useSelector(state => state.data) Extracts state with automatic re-render on changes Access store state
useDispatch const dispatch = useDispatch() Returns dispatch function to trigger actions Update state

Example: Redux Toolkit with RTK Query complete setup

// store.ts
import { configureStore } from '@reduxjs/toolkit';
import { setupListeners } from '@reduxjs/toolkit/query';
import { api } from './api';
import counterReducer from './counterSlice';

export const store = configureStore({
  reducer: {
    counter: counterReducer,
    [api.reducerPath]: api.reducer,
  },
  middleware: (getDefaultMiddleware) =>
    getDefaultMiddleware().concat(api.middleware),
});

setupListeners(store.dispatch);

// counterSlice.ts
import { createSlice, PayloadAction } from '@reduxjs/toolkit';

interface CounterState {
  value: number;
  status: 'idle' | 'loading';
}

const counterSlice = createSlice({
  name: 'counter',
  initialState: { value: 0, status: 'idle' } as CounterState,
  reducers: {
    increment: (state) => {
      state.value += 1;
    },
    decrement: (state) => {
      state.value -= 1;
    },
    incrementByAmount: (state, action: PayloadAction<number>) => {
      state.value += action.payload;
    },
  },
});

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

// api.ts - RTK Query
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react';

export const api = createApi({
  reducerPath: 'api',
  baseQuery: fetchBaseQuery({ baseUrl: '/api' }),
  tagTypes: ['User', 'Post'],
  endpoints: (builder) => ({
    getUsers: builder.query<User[], void>({
      query: () => '/users',
      providesTags: ['User'],
    }),
    getUserById: builder.query<User, string>({
      query: (id) => `/users/${id}`,
      providesTags: (result, error, id) => [{ type: 'User', id }],
    }),
    createUser: builder.mutation<User, Partial<User>>({
      query: (body) => ({
        url: '/users',
        method: 'POST',
        body,
      }),
      invalidatesTags: ['User'],
    }),
  }),
});

export const { useGetUsersQuery, useGetUserByIdQuery, useCreateUserMutation } = api;

// Component usage
function UserList() {
  const { data: users, isLoading, error } = useGetUsersQuery();
  const [createUser] = useCreateUserMutation();

  if (isLoading) return <div>Loading...</div>;
  if (error) return <div>Error loading users</div>;

  return (
    <div>
      {users?.map(user => <div key={user.id}>{user.name}</div>)}
      <button onClick={() => createUser({ name: 'New User' })}>
        Add User
      </button>
    </div>
  );
}

2. Zustand Lightweight Store Creation

Feature Syntax Description Use Case
create create((set) => ({ ...state })) Creates store with state and actions in single function Simple global state
set set((state) => ({ count: state.count + 1 })) Updates state immutably, triggers re-renders State mutations
get get().propertyName Reads current state without subscription Access state in actions
subscribe store.subscribe((state) => {}) Listen to state changes outside components Logging, persistence
persist persist(store, { name: 'key' }) Middleware for localStorage/sessionStorage persistence State persistence
immer immer((state) => { state.value++ }) Allows mutable syntax with Immer library Nested state updates

Example: Zustand store with middleware and actions

import { create } from 'zustand';
import { persist, devtools } from 'zustand/middleware';
import { immer } from 'zustand/middleware/immer';

// Basic store
const useCountStore = create((set) => ({
  count: 0,
  increment: () => set((state) => ({ count: state.count + 1 })),
  decrement: () => set((state) => ({ count: state.count - 1 })),
  reset: () => set({ count: 0 }),
}));

// Advanced store with TypeScript, persist, and devtools
interface BearState {
  bears: number;
  fish: number;
  actions: {
    addBear: () => void;
    removeBear: () => void;
    addFish: (amount: number) => void;
  };
}

const useBearStore = create<BearState>()(
  devtools(
    persist(
      immer((set, get) => ({
        bears: 0,
        fish: 0,
        actions: {
          addBear: () => set((state) => { state.bears += 1; }),
          removeBear: () => set((state) => { state.bears -= 1; }),
          addFish: (amount) => set((state) => { 
            state.fish += amount; 
          }),
        },
      })),
      { name: 'bear-storage' }
    )
  )
);

// Separate selectors for optimization
const useBears = () => useBearStore((state) => state.bears);
const useActions = () => useBearStore((state) => state.actions);

// Component usage
function BearCounter() {
  const bears = useBears();
  const { addBear, removeBear } = useActions();

  return (
    <div>
      <h1>Bears: {bears}</h1>
      <button onClick={addBear}>Add Bear</button>
      <button onClick={removeBear}>Remove Bear</button>
    </div>
  );
}

// Async actions
const useUserStore = create((set, get) => ({
  user: null,
  loading: false,
  fetchUser: async (id) => {
    set({ loading: true });
    const response = await fetch(`/api/users/${id}`);
    const user = await response.json();
    set({ user, loading: false });
  },
}));

3. Context API Provider Pattern

Feature Syntax Description Use Case
createContext const Context = createContext(defaultValue) Creates context object for sharing data Global state initialization
Provider <Context.Provider value={data}> Makes context value available to descendants Wrapping component tree
useContext const value = useContext(Context) Consumes context value in child components Access shared state
Custom Hook Pattern const useAuth = () => useContext(AuthContext) Encapsulates context with validation and helpers Better DX, type safety
Multiple Contexts <Theme><Auth><App/></Auth></Theme> Compose multiple context providers Separation of concerns

Example: Context API with TypeScript and custom hook

// AuthContext.tsx
import { createContext, useContext, useState, ReactNode } from 'react';

interface User {
  id: string;
  name: string;
  email: string;
}

interface AuthContextType {
  user: User | null;
  login: (email: string, password: string) => Promise<void>;
  logout: () => void;
  isAuthenticated: boolean;
}

const AuthContext = createContext<AuthContextType | undefined>(undefined);

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

// Provider component
export function AuthProvider({ children }: { children: ReactNode }) {
  const [user, setUser] = useState<User | null>(null);

  const login = async (email: string, password: string) => {
    const response = await fetch('/api/login', {
      method: 'POST',
      body: JSON.stringify({ email, password }),
    });
    const userData = await response.json();
    setUser(userData);
  };

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

  const value = {
    user,
    login,
    logout,
    isAuthenticated: !!user,
  };

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

// Usage in component
function Profile() {
  const { user, logout, isAuthenticated } = useAuth();

  if (!isAuthenticated) {
    return <div>Please login</div>;
  }

  return (
    <div>
      <h1>Welcome, {user?.name}</h1>
      <button onClick={logout}>Logout</button>
    </div>
  );
}

// App.tsx with provider
function App() {
  return (
    <AuthProvider>
      <Profile />
    </AuthProvider>
  );
}
Note: Context API re-renders all consumers when value changes. For performance-critical apps, split contexts or use useMemo on the value object.

4. Recoil Atom Selector Implementation

Feature Syntax Description Use Case
atom atom({ key: 'id', default: value }) Unit of state that components can subscribe to Shared state pieces
selector selector({ key: 'id', get: ({ get }) => {} }) Derived state from atoms or other selectors Computed values
useRecoilState const [val, setVal] = useRecoilState(atom) Read and write atom state (like useState) Read/write state
useRecoilValue const val = useRecoilValue(atom) Read-only access to atom/selector value Read-only state
useSetRecoilState const setVal = useSetRecoilState(atom) Write-only access without subscribing to changes Write-only operations
atomFamily atomFamily({ key: 'id', default: (param) => {} }) Creates dynamic atoms based on parameters List items, dynamic data
selectorFamily selectorFamily({ key: 'id', get: (param) => {} }) Creates parameterized selectors Filtered/transformed data

Example: Recoil atoms, selectors, and families

import { 
  atom, 
  selector, 
  atomFamily, 
  selectorFamily,
  useRecoilState, 
  useRecoilValue 
} from 'recoil';

// Basic atom
const textState = atom({
  key: 'textState',
  default: '',
});

// Selector for derived state
const charCountState = selector({
  key: 'charCountState',
  get: ({ get }) => {
    const text = get(textState);
    return text.length;
  },
});

// Async selector
const currentUserQuery = selector({
  key: 'currentUserQuery',
  get: async ({ get }) => {
    const userId = get(currentUserIdState);
    const response = await fetch(`/api/users/${userId}`);
    return response.json();
  },
});

// Atom family for dynamic state
const todoState = atomFamily({
  key: 'todoState',
  default: (id) => ({
    id,
    text: '',
    completed: false,
  }),
});

// Selector family for filtered data
const filteredTodosSelector = selectorFamily({
  key: 'filteredTodosSelector',
  get: (filter) => ({ get }) => {
    const todos = get(todoListState);
    switch (filter) {
      case 'completed':
        return todos.filter(todo => todo.completed);
      case 'active':
        return todos.filter(todo => !todo.completed);
      default:
        return todos;
    }
  },
});

// Component usage
function TextInput() {
  const [text, setText] = useRecoilState(textState);
  const charCount = useRecoilValue(charCountState);

  return (
    <div>
      <input value={text} onChange={(e) => setText(e.target.value)} />
      <p>Character Count: {charCount}</p>
    </div>
  );
}

// App with RecoilRoot
import { RecoilRoot } from 'recoil';

function App() {
  return (
    <RecoilRoot>
      <TextInput />
    </RecoilRoot>
  );
}

5. MobX Observable State Trees

Feature Syntax Description Use Case
makeObservable makeObservable(this, { prop: observable }) Makes class properties reactive Class-based stores
makeAutoObservable makeAutoObservable(this) Automatically makes all properties observable Simplified store setup
observable observable({ prop: value }) Creates observable object/array/map Object state
computed get computed() { return this.x * 2 } Derived values automatically recalculated Calculated properties
action @action method() { this.value = x } Functions that modify state State mutations
reaction reaction(() => data, (data) => {}) Side effects triggered by observable changes Logging, persistence
observer observer(Component) Makes React component reactive to observables Component observation

Example: MobX store with observer components

import { makeAutoObservable, runInAction } from 'mobx';
import { observer } from 'mobx-react-lite';

// Store class
class TodoStore {
  todos = [];
  filter = 'all';

  constructor() {
    makeAutoObservable(this);
  }

  // Computed property
  get filteredTodos() {
    switch (this.filter) {
      case 'active':
        return this.todos.filter(todo => !todo.completed);
      case 'completed':
        return this.todos.filter(todo => todo.completed);
      default:
        return this.todos;
    }
  }

  get completedCount() {
    return this.todos.filter(todo => todo.completed).length;
  }

  // Actions
  addTodo(text) {
    this.todos.push({
      id: Date.now(),
      text,
      completed: false,
    });
  }

  toggleTodo(id) {
    const todo = this.todos.find(t => t.id === id);
    if (todo) {
      todo.completed = !todo.completed;
    }
  }

  setFilter(filter) {
    this.filter = filter;
  }

  // Async action
  async fetchTodos() {
    const response = await fetch('/api/todos');
    const todos = await response.json();
    runInAction(() => {
      this.todos = todos;
    });
  }
}

// Create store instance
const todoStore = new TodoStore();

// Observer component - automatically re-renders on observable changes
const TodoList = observer(() => {
  const { filteredTodos, addTodo, toggleTodo } = todoStore;

  return (
    <div>
      <ul>
        {filteredTodos.map(todo => (
          <li key={todo.id}>
            <input
              type="checkbox"
              checked={todo.completed}
              onChange={() => toggleTodo(todo.id)}
            />
            {todo.text}
          </li>
        ))}
      </ul>
      <button onClick={() => addTodo('New Todo')}>Add</button>
    </div>
  );
});

// React Context for store
import { createContext, useContext } from 'react';

const StoreContext = createContext(todoStore);

export const useStore = () => useContext(StoreContext);

6. SWR Data Fetching Caching

Feature Syntax Description Use Case
useSWR const { data, error } = useSWR(key, fetcher) Hook for data fetching with built-in caching API data fetching
mutate mutate(key, data, options) Manually update cache and revalidate Optimistic updates
useSWRMutation const { trigger } = useSWRMutation(key, fetcher) Hook for mutations (POST, PUT, DELETE) Data modifications
SWRConfig <SWRConfig value={{ options }}> Global configuration provider Default settings
Revalidation { revalidateOnFocus: true } Auto-refetch on window focus, reconnect, interval Fresh data strategy
Pagination useSWRInfinite(getKey, fetcher) Infinite loading/pagination hook List pagination

Example: SWR with mutations and optimistic updates

import useSWR, { mutate, useSWRConfig } from 'swr';
import useSWRMutation from 'swr/mutation';

// Fetcher function
const fetcher = (url) => fetch(url).then(res => res.json());

// Basic usage
function UserProfile({ userId }) {
  const { data, error, isLoading, mutate } = useSWR(
    `/api/users/${userId}`,
    fetcher,
    {
      revalidateOnFocus: true,
      revalidateOnReconnect: true,
      dedupingInterval: 5000,
    }
  );

  if (isLoading) return <div>Loading...</div>;
  if (error) return <div>Error: {error.message}</div>;

  return (
    <div>
      <h1>{data.name}</h1>
      <button onClick={() => mutate()}>Refresh</button>
    </div>
  );
}

// Mutation with optimistic update
async function updateUser(url, { arg }) {
  await fetch(url, {
    method: 'PUT',
    body: JSON.stringify(arg),
  });
}

function EditProfile({ userId }) {
  const { trigger } = useSWRMutation(`/api/users/${userId}`, updateUser);

  const handleUpdate = async (updates) => {
    // Optimistic update
    mutate(
      `/api/users/${userId}`,
      (current) => ({ ...current, ...updates }),
      false // Don't revalidate yet
    );

    try {
      await trigger(updates);
    } catch (error) {
      // Revert on error
      mutate(`/api/users/${userId}`);
    }
  };

  return <button onClick={() => handleUpdate({ name: 'New Name' })}>Update</button>;
}

// Global configuration
import { SWRConfig } from 'swr';

function App() {
  return (
    <SWRConfig
      value={{
        refreshInterval: 30000,
        fetcher: (url) => fetch(url).then(res => res.json()),
        onError: (error) => console.error(error),
      }}
    >
      <UserProfile userId="123" />
    </SWRConfig>
  );
}

// Infinite loading
import useSWRInfinite from 'swr/infinite';

function UserList() {
  const getKey = (pageIndex, previousPageData) => {
    if (previousPageData && !previousPageData.hasMore) return null;
    return `/api/users?page=${pageIndex}&limit=10`;
  };

  const { data, size, setSize, isLoading } = useSWRInfinite(getKey, fetcher);

  const users = data ? data.flatMap(page => page.users) : [];
  const hasMore = data?.[data.length - 1]?.hasMore;

  return (
    <div>
      {users.map(user => <div key={user.id}>{user.name}</div>)}
      {hasMore && (
        <button onClick={() => setSize(size + 1)}>Load More</button>
      )}
    </div>
  );
}

State Management Comparison

Library Best For Learning Curve Bundle Size
Redux Toolkit Complex apps, DevTools, middleware Medium ~12KB
Zustand Simple API, minimal boilerplate Low ~1KB
Context API Small apps, theme/auth Low Built-in
Recoil Atomic state, derived values Medium ~21KB
MobX OOP style, automatic tracking Medium ~16KB
SWR Server state, caching, revalidation Low ~5KB