External State Management Library Integration

1. Redux Toolkit (RTK) Integration Patterns

Feature API Description Use Case
configureStore configureStore({ reducer }) Create Redux store with good defaults (DevTools, middleware) Single store setup with automatic configuration
createSlice createSlice({ name, initialState, reducers }) Generate action creators and reducers automatically Reduce boilerplate, use Immer for immutability
createAsyncThunk createAsyncThunk('name', async (arg) => ...) Handle async logic with pending/fulfilled/rejected actions API calls with automatic loading/error state
createEntityAdapter createEntityAdapter() Normalized state with CRUD reducers and selectors Managing collections of entities (users, posts, etc.)
createSelector createSelector([input], (data) => result) Memoized selectors for derived state Compute derived data without re-renders
RTK Query createApi({ endpoints }) Data fetching and caching solution Replace manual async thunks with auto-caching API layer
useSelector useSelector(state => state.slice) Extract data from Redux store in components Subscribe to specific state slices
useDispatch const dispatch = useDispatch() Get dispatch function for triggering actions Update state via action creators

Example: Complete Redux Toolkit setup with slice

// counterSlice.js
import { createSlice } from '@reduxjs/toolkit';

const counterSlice = createSlice({
  name: 'counter',
  initialState: { value: 0, history: [] },
  reducers: {
    increment: (state) => {
      state.value += 1; // Immer allows "mutations"
      state.history.push({ action: 'increment', timestamp: Date.now() });
    },
    decrement: (state) => {
      state.value -= 1;
    },
    incrementByAmount: (state, action) => {
      state.value += action.payload;
    },
    reset: (state) => {
      state.value = 0;
      state.history = [];
    }
  }
});

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

// store.js
import { configureStore } from '@reduxjs/toolkit';
import counterReducer from './counterSlice';

export const store = configureStore({
  reducer: {
    counter: counterReducer
  }
});

// App.js
import { Provider } from 'react-redux';
import { store } from './store';

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

// Counter.js
import { useSelector, useDispatch } from 'react-redux';
import { increment, decrement, reset } from './counterSlice';

function Counter() {
  const count = useSelector(state => state.counter.value);
  const dispatch = useDispatch();
  
  return (
    <div>
      <h1>{count}</h1>
      <button onClick={() => dispatch(increment())}>+</button>
      <button onClick={() => dispatch(decrement())}>-</button>
      <button onClick={() => dispatch(reset())}>Reset</button>
    </div>
  );
}

Example: Async thunk for API calls

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

// Async thunk
export const fetchUsers = createAsyncThunk(
  'users/fetchUsers',
  async (page, { rejectWithValue }) => {
    try {
      const response = await fetch(`/api/users?page=${page}`);
      return await response.json();
    } catch (error) {
      return rejectWithValue(error.message);
    }
  }
);

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

// Component usage
function UserList() {
  const { entities, loading, error } = useSelector(state => state.users);
  const dispatch = useDispatch();
  
  useEffect(() => {
    dispatch(fetchUsers(1));
  }, [dispatch]);
  
  if (loading) return <Spinner />;
  if (error) return <Error message={error} />;
  
  return (
    <ul>
      {entities.map(user => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  );
}
Note: Redux Toolkit is the recommended way to use Redux. It includes Immer for immutable updates, Redux Thunk for async, and Redux DevTools by default. Use RTK Query for data fetching instead of manually writing thunks.

2. Zustand Store Implementation and Usage

Feature API Description Benefits
create create((set, get) => ({ ... })) Create a hook-based store No providers, minimal boilerplate, auto-generates hooks
set set({ field: value }) Merge updates into state Partial updates, can be used with functions
get get() Read current state in actions Access state without React rendering
Selectors useStore(state => state.field) Subscribe to specific state slices Automatic re-render optimization
Middleware persist, devtools, immer Extend store with additional functionality Persist to storage, debug, immutable updates
Transient Updates set({ ... }, true) Update without triggering subscribers Temporary state that doesn't cause re-renders
subscribe store.subscribe(listener) Listen to state changes outside React Integration with non-React code
Shallow Equality useStore(selector, shallow) Shallow comparison for object selectors Prevent re-renders when object content unchanged

Example: Complete Zustand store with actions

import { create } from 'zustand';

// Create store
const useStore = create((set, get) => ({
  // State
  bears: 0,
  fish: 0,
  
  // Actions
  increasePopulation: () => set((state) => ({ bears: state.bears + 1 })),
  removeAllBears: () => set({ bears: 0 }),
  
  // Action using get() to read current state
  eatFish: () => set((state) => ({ 
    fish: state.fish - 1,
    bears: state.bears + 0.1 
  })),
  
  // Async action
  fetchInitialData: async () => {
    const response = await fetch('/api/wildlife');
    const data = await response.json();
    set({ bears: data.bears, fish: data.fish });
  },
  
  // Computed getter
  getTotalAnimals: () => {
    const state = get();
    return state.bears + state.fish;
  }
}));

// Component usage - subscribe to entire store
function AllCounts() {
  const { bears, fish } = useStore();
  return <div>Bears: {bears}, Fish: {fish}</div>;
}

// Component usage - subscribe to specific field
function BearCounter() {
  const bears = useStore(state => state.bears);
  return <h1>{bears} bears</h1>;
}

// Component usage - access actions
function Controls() {
  const increasePopulation = useStore(state => state.increasePopulation);
  const removeAllBears = useStore(state => state.removeAllBears);
  
  return (
    <div>
      <button onClick={increasePopulation}>Add Bear</button>
      <button onClick={removeAllBears}>Remove All</button>
    </div>
  );
}

Example: Zustand with persistence and DevTools

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

const useStore = create(
  devtools(
    persist(
      immer((set) => ({
        todos: [],
        
        addTodo: (text) => set((state) => {
          // Immer allows "mutations"
          state.todos.push({ 
            id: Date.now(), 
            text, 
            completed: false 
          });
        }),
        
        toggleTodo: (id) => set((state) => {
          const todo = state.todos.find(t => t.id === id);
          if (todo) todo.completed = !todo.completed;
        }),
        
        removeTodo: (id) => set((state) => {
          state.todos = state.todos.filter(t => t.id !== id);
        })
      })),
      { name: 'todo-storage' } // localStorage key
    )
  )
);

// Slice pattern - multiple stores
const useUserStore = create((set) => ({
  user: null,
  setUser: (user) => set({ user }),
  logout: () => set({ user: null })
}));

const useCartStore = create((set) => ({
  items: [],
  addItem: (item) => set((state) => ({ 
    items: [...state.items, item] 
  }))
}));

// Use multiple stores in component
function App() {
  const user = useUserStore(state => state.user);
  const cartItems = useCartStore(state => state.items);
  
  return <div>{user?.name} has {cartItems.length} items</div>;
}
Note: Zustand is lightweight (~1KB), requires no Provider, and supports TypeScript excellently. Use selectors to optimize re-renders. Combine with middleware for persistence, DevTools, and Immer integration.

3. Jotai Atomic State Management

Concept API Description Use Case
Atom atom(initialValue) Primitive unit of state (like React state) Small, independent pieces of state
useAtom const [value, setValue] = useAtom(atom) Read and write atom value useState-like API for atoms
useAtomValue const value = useAtomValue(atom) Read-only access to atom Subscribe to value without write capability
useSetAtom const setValue = useSetAtom(atom) Write-only access to atom Update without subscribing to value
Derived Atoms atom((get) => get(otherAtom) * 2) Compute value from other atoms Derived/computed state with automatic dependencies
Async Atoms atom(async (get) => await fetch(...)) Atoms with async read function Data fetching with Suspense integration
Write-only Atoms atom(null, (get, set, arg) => ...) Atoms for actions without state Action creators that update other atoms
atomFamily atomFamily((param) => atom(...)) Create atoms dynamically based on parameters Parameterized atoms (e.g., user atoms by ID)

Example: Basic Jotai atoms and derived state

import { atom, useAtom, useAtomValue } from 'jotai';

// Primitive atoms
const countAtom = atom(0);
const nameAtom = atom('Guest');

// Derived atom (read-only)
const doubleCountAtom = atom((get) => get(countAtom) * 2);

// Derived atom with dependencies
const greetingAtom = atom((get) => {
  const name = get(nameAtom);
  const count = get(countAtom);
  return `Hello ${name}, count is ${count}`;
});

// Write-only action atom
const incrementAtom = atom(
  null, // no read
  (get, set, amount) => {
    set(countAtom, get(countAtom) + amount);
  }
);

// Component usage
function Counter() {
  const [count, setCount] = useAtom(countAtom);
  const doubleCount = useAtomValue(doubleCountAtom);
  const increment = useSetAtom(incrementAtom);
  
  return (
    <div>
      <p>Count: {count}, Double: {doubleCount}</p>
      <button onClick={() => setCount(c => c + 1)}>+1</button>
      <button onClick={() => increment(5)}>+5</button>
    </div>
  );
}

function Greeting() {
  const greeting = useAtomValue(greetingAtom);
  return <h1>{greeting}</h1>;
}

// Provider (optional, for scoping)
import { Provider } from 'jotai';

function App() {
  return (
    <Provider>
      <Counter />
      <Greeting />
    </Provider>
  );
}

Example: Async atoms with Suspense

import { atom, useAtomValue } from 'jotai';
import { Suspense } from 'react';

// Async atom for fetching user
const userIdAtom = atom(1);
const userAtom = atom(async (get) => {
  const userId = get(userIdAtom);
  const response = await fetch(`/api/users/${userId}`);
  return response.json();
});

// Derived async atom
const userPostsAtom = atom(async (get) => {
  const user = await get(userAtom);
  const response = await fetch(`/api/users/${user.id}/posts`);
  return response.json();
});

function UserProfile() {
  const user = useAtomValue(userAtom); // Suspends while loading
  return <div>{user.name}</div>;
}

function UserPosts() {
  const posts = useAtomValue(userPostsAtom); // Suspends
  return (
    <ul>
      {posts.map(p => <li key={p.id}>{p.title}</li>)}
    </ul>
  );
}

function App() {
  return (
    <Suspense fallback={<div>Loading...</div>}>
      <UserProfile />
      <UserPosts />
    </Suspense>
  );
}

// atomFamily for parameterized atoms
import { atomFamily } from 'jotai/utils';

const todoAtomFamily = atomFamily((id) =>
  atom(async () => {
    const res = await fetch(`/api/todos/${id}`);
    return res.json();
  })
);

function Todo({ id }) {
  const todo = useAtomValue(todoAtomFamily(id));
  return <div>{todo.title}</div>;
}
Note: Jotai provides bottom-up atomic state management. Atoms are defined globally but values are stored per Provider scope. Excellent TypeScript support, built-in Suspense integration, and minimal boilerplate.

4. Valtio Proxy-based State Management

Feature API Description Characteristics
proxy const state = proxy({ ... }) Create mutable proxy state object Mutate directly, auto-detects changes with Proxies
useSnapshot const snap = useSnapshot(state) Get immutable snapshot for rendering Automatic re-render on used properties
subscribe subscribe(state, callback) Listen to state changes React to mutations outside components
snapshot const snap = snapshot(state) Get snapshot outside React Read immutable version of mutable state
derive derive({ computed: (get) => ... }) Create derived/computed properties Automatically recompute when dependencies change
ref ref(value) Mark value as non-reactive Store objects that shouldn't trigger updates
Nested Proxies Automatic for objects/arrays Deep reactivity for nested structures No need to spread or copy nested objects
Render Optimization Auto-tracks property access Re-render only when accessed properties change Fine-grained reactivity without selectors

Example: Basic Valtio state with mutations

import { proxy, useSnapshot } from 'valtio';

// Create mutable state
const state = proxy({
  count: 0,
  text: 'Hello',
  nested: {
    value: 42
  },
  todos: []
});

// Mutate state directly (outside React)
function increment() {
  state.count++; // Direct mutation!
}

function addTodo(text) {
  state.todos.push({ // Array mutation
    id: Date.now(),
    text,
    completed: false
  });
}

function toggleTodo(id) {
  const todo = state.todos.find(t => t.id === id);
  if (todo) {
    todo.completed = !todo.completed; // Nested mutation
  }
}

// Component usage
function Counter() {
  const snap = useSnapshot(state);
  
  return (
    <div>
      <p>{snap.count}</p>
      <button onClick={increment}>Increment</button>
    </div>
  );
}

function TodoList() {
  const snap = useSnapshot(state);
  
  return (
    <ul>
      {snap.todos.map(todo => (
        <li key={todo.id} onClick={() => toggleTodo(todo.id)}>
          {todo.text} {todo.completed ? '✓' : ''}
        </li>
      ))}
    </ul>
  );
}

// Only re-renders when count changes (not when todos change)
function CountDisplay() {
  const snap = useSnapshot(state);
  return <div>Count: {snap.count}</div>;
}

Example: Derived state and computed properties

import { proxy, useSnapshot, derive } from 'valtio';

const state = proxy({
  items: [
    { id: 1, name: 'Apple', price: 1.2, quantity: 5 },
    { id: 2, name: 'Banana', price: 0.8, quantity: 3 }
  ],
  taxRate: 0.1
});

// Add derived/computed properties
const derived = derive({
  subtotal: (get) => {
    const items = get(state).items;
    return items.reduce((sum, item) => 
      sum + item.price * item.quantity, 0
    );
  },
  tax: (get) => {
    return get(derived).subtotal * get(state).taxRate;
  },
  total: (get) => {
    return get(derived).subtotal + get(derived).tax;
  }
});

function Cart() {
  const snap = useSnapshot(state);
  const derivedSnap = useSnapshot(derived);
  
  return (
    <div>
      {snap.items.map(item => (
        <div key={item.id}>
          {item.name}: ${item.price} × {item.quantity}
        </div>
      ))}
      <hr />
      <div>Subtotal: ${derivedSnap.subtotal.toFixed(2)}</div>
      <div>Tax: ${derivedSnap.tax.toFixed(2)}</div>
      <div>Total: ${derivedSnap.total.toFixed(2)}</div>
    </div>
  );
}

// Update quantity
function updateQuantity(id, quantity) {
  const item = state.items.find(i => i.id === id);
  if (item) {
    item.quantity = quantity;
    // derived values automatically recompute
  }
}
Note: Valtio enables mutable-style updates with immutable snapshots for React. Use for TypeScript projects needing simple, direct mutations. Automatic fine-grained reactivity without manual selectors. Consider Vue-like developer experience.

5. Recoil Experimental State Management

Concept API Description Status
atom atom({ key, default }) Unit of state with unique key BETA Requires unique string key
selector selector({ key, get }) Derived/computed state from atoms Pure function of atoms/selectors
useRecoilState const [value, setValue] = useRecoilState(atom) Read and write atom (like useState) Similar to React hooks API
useRecoilValue const value = useRecoilValue(atom) Read-only access to atom/selector Subscribe without write capability
useSetRecoilState const setValue = useSetRecoilState(atom) Write-only access to atom Update without subscribing
atomFamily atomFamily({ key, default: (param) => ... }) Create atoms dynamically Parameterized atoms for collections
selectorFamily selectorFamily({ key, get: (param) => ... }) Parameterized selectors Derived state with parameters
Async Selectors get: async ({ get }) => await ... Async data fetching in selectors BETA Experimental feature

Example: Recoil atoms and selectors

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

// Atoms (must have unique keys)
const textState = atom({
  key: 'textState',
  default: ''
});

const listState = atom({
  key: 'listState',
  default: []
});

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

// Filtered list selector
const filteredListState = selector({
  key: 'filteredListState',
  get: ({ get }) => {
    const list = get(listState);
    const filter = get(textState);
    return list.filter(item => 
      item.toLowerCase().includes(filter.toLowerCase())
    );
  }
});

// Component usage
function TextInput() {
  const [text, setText] = useRecoilState(textState);
  const charCount = useRecoilValue(charCountState);
  
  return (
    <div>
      <input value={text} onChange={(e) => setText(e.target.value)} />
      <div>Character Count: {charCount}</div>
    </div>
  );
}

function FilteredList() {
  const filteredItems = useRecoilValue(filteredListState);
  
  return (
    <ul>
      {filteredItems.map((item, i) => (
        <li key={i}>{item}</li>
      ))}
    </ul>
  );
}

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

Example: Async selectors and atom families

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

// Async selector for current user
const currentUserQuery = selector({
  key: 'currentUserQuery',
  get: async () => {
    const response = await fetch('/api/current-user');
    return response.json();
  }
});

// Atom family for user data by ID
const userState = atomFamily({
  key: 'userState',
  default: selectorFamily({
    key: 'userState/default',
    get: (userId) => async () => {
      const response = await fetch(`/api/users/${userId}`);
      return response.json();
    }
  })
});

// Selector family for user posts
const userPostsQuery = selectorFamily({
  key: 'userPostsQuery',
  get: (userId) => async ({ get }) => {
    // Can depend on other atoms/selectors
    const user = get(userState(userId));
    const response = await fetch(`/api/users/${userId}/posts`);
    return response.json();
  }
});

// Component usage with Suspense
import { Suspense } from 'react';

function CurrentUser() {
  const user = useRecoilValue(currentUserQuery);
  return <div>{user.name}</div>;
}

function UserProfile({ userId }) {
  const user = useRecoilValue(userState(userId));
  const posts = useRecoilValue(userPostsQuery(userId));
  
  return (
    <div>
      <h1>{user.name}</h1>
      <ul>
        {posts.map(p => <li key={p.id}>{p.title}</li>)}
      </ul>
    </div>
  );
}

function App() {
  return (
    <RecoilRoot>
      <Suspense fallback={<div>Loading...</div>}>
        <CurrentUser />
        <UserProfile userId={1} />
      </Suspense>
    </RecoilRoot>
  );
}
Warning: Recoil is experimental and development has slowed. Consider Jotai as a more actively maintained alternative with similar atomic state patterns. Recoil requires unique string keys for all atoms which can be cumbersome.

6. Custom State Management Library Creation

Approach Implementation Complexity Features
useSyncExternalStore useSyncExternalStore(subscribe, getSnapshot) Low - React 18 built-in Sync external state with React, concurrent-safe
Context + useReducer createContext + Provider Low - Uses React primitives Simple global state, requires Provider wrapping
Observable Pattern subscribe/unsubscribe listeners Medium - Manual subscription management Pub/sub system, works outside React
Proxy-based new Proxy(target, handler) Medium - Requires Proxy knowledge Auto-tracking, mutable-style updates
Immer Integration produce(state, draft => ...) Low - Library dependency Immutable updates with mutable syntax
Middleware Support compose(middleware1, middleware2) High - Complex composition Extensibility via plugins (logging, persist, devtools)
Selector System createSelector(deps, compute) Medium - Memoization logic Derived state with dependency tracking
Time Travel history = [states], pointer High - History management Undo/redo, debugging, replay

Example: Custom store with useSyncExternalStore

import { useSyncExternalStore } from 'react';

// Create simple store
function createStore(initialState) {
  let state = initialState;
  const listeners = new Set();
  
  const getState = () => state;
  
  const setState = (newState) => {
    state = typeof newState === 'function' 
      ? newState(state) 
      : newState;
    listeners.forEach(listener => listener());
  };
  
  const subscribe = (listener) => {
    listeners.add(listener);
    return () => listeners.delete(listener);
  };
  
  return { getState, setState, subscribe };
}

// Create store instance
const counterStore = createStore({ count: 0 });

// Custom hook
function useCounter() {
  const state = useSyncExternalStore(
    counterStore.subscribe,
    counterStore.getState
  );
  
  const increment = () => {
    counterStore.setState(s => ({ count: s.count + 1 }));
  };
  
  const decrement = () => {
    counterStore.setState(s => ({ count: s.count - 1 }));
  };
  
  return { count: state.count, increment, decrement };
}

// Component usage
function Counter() {
  const { count, increment, decrement } = useCounter();
  
  return (
    <div>
      <h1>{count}</h1>
      <button onClick={increment}>+</button>
      <button onClick={decrement}>-</button>
    </div>
  );
}

Example: Store with selectors and computed values

function createStoreWithSelectors(initialState) {
  let state = initialState;
  const listeners = new Set();
  const selectorCache = new Map();
  
  const getState = () => state;
  
  const setState = (updater) => {
    const newState = typeof updater === 'function' 
      ? updater(state) 
      : updater;
    
    if (newState !== state) {
      state = newState;
      selectorCache.clear(); // Invalidate cache
      listeners.forEach(listener => listener());
    }
  };
  
  const subscribe = (listener) => {
    listeners.add(listener);
    return () => listeners.delete(listener);
  };
  
  // Selector with memoization
  const createSelector = (selector) => {
    return () => {
      const cacheKey = selector.toString();
      
      if (!selectorCache.has(cacheKey)) {
        selectorCache.set(cacheKey, selector(state));
      }
      
      return selectorCache.get(cacheKey);
    };
  };
  
  return { getState, setState, subscribe, createSelector };
}

// Usage
const store = createStoreWithSelectors({
  users: [
    { id: 1, name: 'Alice', age: 30 },
    { id: 2, name: 'Bob', age: 25 }
  ]
});

// Define selectors
const selectAdultUsers = store.createSelector(
  state => state.users.filter(u => u.age >= 18)
);

const selectUserCount = store.createSelector(
  state => state.users.length
);

// Custom hook with selector
function useStoreSelector(selector) {
  return useSyncExternalStore(
    store.subscribe,
    () => selector(store.getState())
  );
}

function UserList() {
  const adults = useStoreSelector(selectAdultUsers);
  const count = useStoreSelector(selectUserCount);
  
  return (
    <div>
      <p>Total users: {count}</p>
      {adults.map(u => <div key={u.id}>{u.name}</div>)}
    </div>
  );
}

Example: Store with middleware support

function createStoreWithMiddleware(initialState, middlewares = []) {
  let state = initialState;
  const listeners = new Set();
  
  // Compose middleware
  const composedMiddleware = middlewares.reduceRight(
    (next, middleware) => middleware(next),
    (newState) => {
      state = newState;
      listeners.forEach(l => l());
    }
  );
  
  const getState = () => state;
  
  const setState = (updater) => {
    const newState = typeof updater === 'function'
      ? updater(state)
      : updater;
    composedMiddleware(newState);
  };
  
  const subscribe = (listener) => {
    listeners.add(listener);
    return () => listeners.delete(listener);
  };
  
  return { getState, setState, subscribe };
}

// Middleware examples
const logger = (next) => (state) => {
  console.log('Previous state:', state);
  next(state);
  console.log('New state:', state);
};

const persist = (key) => (next) => (state) => {
  next(state);
  localStorage.setItem(key, JSON.stringify(state));
};

const devtools = (next) => (state) => {
  if (window.__REDUX_DEVTOOLS_EXTENSION__) {
    window.__REDUX_DEVTOOLS_EXTENSION__.send('UPDATE', state);
  }
  next(state);
};

// Create store with middleware
const store = createStoreWithMiddleware(
  { count: 0 },
  [logger, persist('app-state'), devtools]
);

// Hook
function useStore() {
  return useSyncExternalStore(
    store.subscribe,
    store.getState
  );
}
Note: Custom stores are useful for learning or specific needs. Use useSyncExternalStore (React 18+) for concurrent-safe external state. For production, prefer established libraries (Zustand, Jotai) unless you have unique requirements.

Section 14 Key Takeaways

  • Redux Toolkit - Official Redux with minimal boilerplate, Immer, and RTK Query for data fetching
  • Zustand - Lightweight (1KB), no Provider, hook-based with middleware support
  • Jotai - Atomic bottom-up state, Suspense support, minimal API similar to Recoil
  • Valtio - Proxy-based mutable updates with immutable snapshots, fine-grained reactivity
  • Recoil - Experimental atomic state from Meta, consider Jotai as alternative
  • Custom stores - Use useSyncExternalStore for concurrent-safe external state integration