Intermédiaire 14 min de lecture · 2 882 mots

State Management : Redux Toolkit vs Zustand vs Context API

State Management : Redux Toolkit vs Zustand vs Context API
Intermédiaire · 2026.05.09

Introduction

La gestion d’état est cruciale dans les applications React modernes. En 2025, trois solutions dominent : Redux Toolkit (le standard de l’industrie), Zustand (léger et moderne), et Context API (natif React). Découvrons leurs forces et faiblesses.

1. Context API : La solution native

Setup basique

// src/contexts/CartContext.tsx
import { createContext, useContext, useState, ReactNode } from 'react';

interface CartItem {
  id: number;
  name: string;
  price: number;
  quantity: number;
}

interface CartContextType {
  items: CartItem[];
  addItem: (item: Omit<CartItem, 'quantity'>) => void;
  removeItem: (id: number) => void;
  updateQuantity: (id: number, quantity: number) => void;
  total: number;
  itemCount: number;
}

const CartContext = createContext<CartContextType | undefined>(undefined);

export function CartProvider({ children }: { children: ReactNode }) {
  const [items, setItems] = useState<CartItem[]>([]);

  const addItem = (item: Omit<CartItem, 'quantity'>) => {
    setItems(current => {
      const existing = current.find(i => i.id === item.id);
      if (existing) {
        return current.map(i =>
          i.id === item.id ? { ...i, quantity: i.quantity + 1 } : i
        );
      }
      return [...current, { ...item, quantity: 1 }];
    });
  };

  const removeItem = (id: number) => {
    setItems(current => current.filter(i => i.id !== id));
  };

  const updateQuantity = (id: number, quantity: number) => {
    if (quantity <= 0) {
      removeItem(id);
      return;
    }
    setItems(current =>
      current.map(i => (i.id === id ? { ...i, quantity } : i))
    );
  };

  const total = items.reduce((sum, item) => sum + item.price * item.quantity, 0);
  const itemCount = items.reduce((sum, item) => sum + item.quantity, 0);

  const value = {
    items,
    addItem,
    removeItem,
    updateQuantity,
    total,
    itemCount
  };

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

export function useCart() {
  const context = useContext(CartContext);
  if (!context) {
    throw new Error('useCart must be used within CartProvider');
  }
  return context;
}

Utilisation

// src/App.tsx
import { CartProvider } from './contexts/CartContext';

function App() {
  return (
    <CartProvider>
      <Header />
      <ProductList />
      <Cart />
    </CartProvider>
  );
}

// src/components/Header.tsx
import { useCart } from '@/contexts/CartContext';

function Header() {
  const { itemCount } = useCart();

  return (
    <header>
      # Mon Shop
      <div>Panier: {itemCount} articles</div>
    </header>
  );
}

// src/components/ProductCard.tsx
function ProductCard({ product }: { product: Product }) {
  const { addItem } = useCart();

  return (
    <div>
      ### {product.name}
      <p>{product.price}€</p>
      <button onClick={() => addItem(product)}>
        Ajouter au panier
      </button>
    </div>
  );
}

Optimisation : Éviter les re-renders

// ❌ Problème : tous les consommateurs re-render
const CartContext = createContext({ items: [], addItem: () => {} });

// ✅ Solution 1 : Séparer les contexts
const CartItemsContext = createContext<CartItem[]>([]);
const CartActionsContext = createContext<CartActions | undefined>(undefined);

export function CartProvider({ children }: { children: ReactNode }) {
  const [items, setItems] = useState<CartItem[]>([]);

  const actions = useMemo(() => ({
    addItem: (item: CartItem) => setItems(prev => [...prev, item]),
    removeItem: (id: number) => setItems(prev => prev.filter(i => i.id !== id))
  }), []);

  return (
    <CartItemsContext.Provider value={items}>
      <CartActionsContext.Provider value={actions}>
        {children}
      </CartActionsContext.Provider>
    </CartItemsContext.Provider>
  );
}

// ✅ Solution 2 : useContextSelector (avec use-context-selector)
import { createContext } from 'use-context-selector';

const CartContext = createContext<CartState | undefined>(undefined);

function Header() {
  // Ne re-render que si itemCount change
  const itemCount = useContextSelector(CartContext, v => v.itemCount);
  return <div>Panier: {itemCount}</div>;
}

Avantages et inconvénients

Avantages :

  • Natif React, pas de dépendance
  • Simple pour des états simples
  • TypeScript friendly
  • Parfait pour le theming, i18n, auth
  • Inconvénients :

  • Performance : re-render tous les consommateurs
  • Pas de DevTools
  • Pas de middleware
  • Devient verbeux pour des états complexes
  • 2. Zustand : Simplicité et performance

    Installation

npm install zustand

Store basique

// src/stores/cartStore.ts
import { create } from 'zustand';

interface CartItem {
  id: number;
  name: string;
  price: number;
  quantity: number;
}

interface CartStore {
  items: CartItem[];
  addItem: (item: Omit<CartItem, 'quantity'>) => void;
  removeItem: (id: number) => void;
  updateQuantity: (id: number, quantity: number) => void;
  clearCart: () => void;
  total: () => number;
  itemCount: () => number;
}

export const useCartStore = create<CartStore>((set, get) => ({
  items: [],

  addItem: (item) => set((state) => {
    const existing = state.items.find(i => i.id === item.id);
    if (existing) {
      return {
        items: state.items.map(i =>
          i.id === item.id ? { ...i, quantity: i.quantity + 1 } : i
        )
      };
    }
    return { items: [...state.items, { ...item, quantity: 1 }] };
  }),

  removeItem: (id) => set((state) => ({
    items: state.items.filter(i => i.id !== id)
  })),

  updateQuantity: (id, quantity) => set((state) => ({
    items: state.items.map(i =>
      i.id === id ? { ...i, quantity } : i
    )
  })),

  clearCart: () => set({ items: [] }),

  total: () => {
    const { items } = get();
    return items.reduce((sum, item) => sum + item.price * item.quantity, 0);
  },

  itemCount: () => {
    const { items } = get();
    return items.reduce((sum, item) => sum + item.quantity, 0);
  }
}));

Utilisation

// src/components/Header.tsx
import { useCartStore } from '@/stores/cartStore';

function Header() {
  // Sélecteur : ne re-render que si itemCount change
  const itemCount = useCartStore(state => state.itemCount());

  return (
    <header>
      # Mon Shop
      <div>Panier: {itemCount} articles</div>
    </header>
  );
}

// src/components/ProductCard.tsx
function ProductCard({ product }: { product: Product }) {
  // Sélectionner uniquement la fonction
  const addItem = useCartStore(state => state.addItem);

  return (
    <div>
      ### {product.name}
      <button onClick={() => addItem(product)}>
        Ajouter
      </button>
    </div>
  );
}

// src/components/Cart.tsx
function Cart() {
  const { items, removeItem, total } = useCartStore(state => ({
    items: state.items,
    removeItem: state.removeItem,
    total: state.total()
  }));

  return (
    <div>
      {items.map(item => (
        <div key={item.id}>
          <span>{item.name}</span>
          <span>{item.quantity}</span>
          <button onClick={() => removeItem(item.id)}>×</button>
        </div>
      ))}
      <div>Total: {total}€</div>
    </div>
  );
}

Middleware : Persistence

// src/stores/cartStore.ts
import { create } from 'zustand';
import { persist, createJSONStorage } from 'zustand/middleware';

export const useCartStore = create<CartStore>()(
  persist(
    (set, get) => ({
      items: [],
      addItem: (item) => set((state) => ({ /* ... */ })),
      // ... autres actions
    }),
    {
      name: 'cart-storage', // clé localStorage
      storage: createJSONStorage(() => localStorage),
    }
  )
);

Middleware : Immer (mutations immuables)

import { create } from 'zustand';
import { immer } from 'zustand/middleware/immer';

export const useCartStore = create<CartStore>()(
  immer((set) => ({
    items: [],

    // ✅ Mutations directes grâce à Immer
    addItem: (item) => set((state) => {
      const existing = state.items.find(i => i.id === item.id);
      if (existing) {
        existing.quantity += 1;
      } else {
        state.items.push({ ...item, quantity: 1 });
      }
    }),

    updateQuantity: (id, quantity) => set((state) => {
      const item = state.items.find(i => i.id === id);
      if (item) {
        item.quantity = quantity;
      }
    })
  }))
);

Middleware : DevTools

import { devtools } from 'zustand/middleware';

export const useCartStore = create<CartStore>()(
  devtools(
    persist(
      immer((set) => ({
        // ... store
      })),
      { name: 'cart-storage' }
    ),
    { name: 'CartStore' }
  )
);

Slices pattern (organisation)

// src/stores/slices/userSlice.ts
export interface UserSlice {
  user: User | null;
  login: (user: User) => void;
  logout: () => void;
}

export const createUserSlice = (set: any): UserSlice => ({
  user: null,
  login: (user) => set({ user }),
  logout: () => set({ user: null })
});

// src/stores/slices/cartSlice.ts
export const createCartSlice = (set: any): CartSlice => ({
  items: [],
  addItem: (item) => set((state: any) => ({ /* ... */ }))
});

// src/stores/index.ts
import { create } from 'zustand';
import { createUserSlice } from './slices/userSlice';
import { createCartSlice } from './slices/cartSlice';

type StoreState = UserSlice & CartSlice;

export const useStore = create<StoreState>()((...a) => ({
  ...createUserSlice(...a),
  ...createCartSlice(...a)
}));

Avantages et inconvénients

Avantages :

  • Très léger (~1kb)
  • API simple et intuitive
  • Excellent pour la performance (sélecteurs)
  • Middleware riches (persist, devtools, immer)
  • TypeScript excellent
  • Pas de Provider nécessaire
  • Inconvénients :

  • Moins de structure que Redux
  • Communauté plus petite
  • Moins de middleware tiers
  • 3. Redux Toolkit : Le standard de l’industrie

    Installation

    npm install @reduxjs/toolkit react-redux
    

    Store configuration

    // src/store/index.ts
    import { configureStore } from '@reduxjs/toolkit';
    import cartReducer from './slices/cartSlice';
    import userReducer from './slices/userSlice';
    
    export const store = configureStore({
      reducer: {
        cart: cartReducer,
        user: userReducer
      },
      middleware: (getDefaultMiddleware) =>
        getDefaultMiddleware({
          serializableCheck: false
        })
    });
    
    export type RootState = ReturnType<typeof store.getState>;
    export type AppDispatch = typeof store.dispatch;
    

    Slice avec Redux Toolkit

    // src/store/slices/cartSlice.ts
    import { createSlice, PayloadAction } from '@reduxjs/toolkit';
    
    interface CartItem {
      id: number;
      name: string;
      price: number;
      quantity: number;
    }
    
    interface CartState {
      items: CartItem[];
      loading: boolean;
      error: string | null;
    }
    
    const initialState: CartState = {
      items: [],
      loading: false,
      error: null
    };
    
    const cartSlice = createSlice({
      name: 'cart',
      initialState,
      reducers: {
        addItem: (state, action: PayloadAction<Omit<CartItem, 'quantity'>>) => {
          const existing = state.items.find(i => i.id === action.payload.id);
          if (existing) {
            existing.quantity += 1;
          } else {
            state.items.push({ ...action.payload, quantity: 1 });
          }
        },
    
        removeItem: (state, action: PayloadAction<number>) => {
          state.items = state.items.filter(i => i.id !== action.payload);
        },
    
        updateQuantity: (state, action: PayloadAction<{ id: number; quantity: number }>) => {
          const item = state.items.find(i => i.id === action.payload.id);
          if (item) {
            item.quantity = action.payload.quantity;
          }
        },
    
        clearCart: (state) => {
          state.items = [];
        }
      }
    });
    
    export const { addItem, removeItem, updateQuantity, clearCart } = cartSlice.actions;
    export default cartSlice.reducer;
    
    // Selectors
    export const selectCartItems = (state: RootState) => state.cart.items;
    export const selectCartTotal = (state: RootState) =>
      state.cart.items.reduce((sum, item) => sum + item.price * item.quantity, 0);
    export const selectItemCount = (state: RootState) =>
      state.cart.items.reduce((sum, item) => sum + item.quantity, 0);
    

    Hooks typés

    // src/store/hooks.ts
    import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux';
    import type { RootState, AppDispatch } from './index';
    
    export const useAppDispatch = () => useDispatch<AppDispatch>();
    export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;
    

    Utilisation dans les composants

    // src/App.tsx
    import { Provider } from 'react-redux';
    import { store } from './store';
    
    function App() {
      return (
        <Provider store={store}>
          <Header />
          <ProductList />
          <Cart />
        </Provider>
      );
    }
    
    // src/components/Header.tsx
    import { useAppSelector } from '@/store/hooks';
    import { selectItemCount } from '@/store/slices/cartSlice';
    
    function Header() {
      const itemCount = useAppSelector(selectItemCount);
    
      return (
        <header>
          # Mon Shop
          <div>Panier: {itemCount} articles</div>
        </header>
      );
    }
    
    // src/components/ProductCard.tsx
    import { useAppDispatch } from '@/store/hooks';
    import { addItem } from '@/store/slices/cartSlice';
    
    function ProductCard({ product }: { product: Product }) {
      const dispatch = useAppDispatch();
    
      return (
        <div>
          ### {product.name}
          <button onClick={() => dispatch(addItem(product))}>
            Ajouter
          </button>
        </div>
      );
    }
    

    Async Thunks

    // src/store/slices/productsSlice.ts
    import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';
    
    interface Product {
      id: number;
      name: string;
      price: number;
    }
    
    interface ProductsState {
      items: Product[];
      loading: boolean;
      error: string | null;
    }
    
    const initialState: ProductsState = {
      items: [],
      loading: false,
      error: null
    };
    
    // Async thunk
    export const fetchProducts = createAsyncThunk(
      'products/fetchProducts',
      async (category?: string) => {
        const url = category
          ? /api/products?category=${category}
          : '/api/products';
    
        const response = await fetch(url);
        if (!response.ok) {
          throw new Error('Failed to fetch products');
        }
        return response.json();
      }
    );
    
    const productsSlice = createSlice({
      name: 'products',
      initialState,
      reducers: {},
      extraReducers: (builder) => {
        builder
          .addCase(fetchProducts.pending, (state) => {
            state.loading = true;
            state.error = null;
          })
          .addCase(fetchProducts.fulfilled, (state, action) => {
            state.loading = false;
            state.items = action.payload;
          })
          .addCase(fetchProducts.rejected, (state, action) => {
            state.loading = false;
            state.error = action.error.message || 'An error occurred';
          });
      }
    });
    
    export default productsSlice.reducer;
    

    RTK Query : Data fetching

    // src/store/api/productsApi.ts
    import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react';
    
    interface Product {
      id: number;
      name: string;
      price: number;
    }
    
    export const productsApi = createApi({
      reducerPath: 'productsApi',
      baseQuery: fetchBaseQuery({ baseUrl: '/api' }),
      tagTypes: ['Products'],
      endpoints: (builder) => ({
        getProducts: builder.query<Product[], void>({
          query: () => 'products',
          providesTags: ['Products']
        }),
    
        getProduct: builder.query<Product, number>({
          query: (id) => products/${id},
          providesTags: (result, error, id) => [{ type: 'Products', id }]
        }),
    
        createProduct: builder.mutation<Product, Omit<Product, 'id'>>({
          query: (body) => ({
            url: 'products',
            method: 'POST',
            body
          }),
          invalidatesTags: ['Products']
        }),
    
        updateProduct: builder.mutation<Product, Product>({
          query: ({ id, ...body }) => ({
            url: products/${id},
            method: 'PUT',
            body
          }),
          invalidatesTags: (result, error, { id }) => [{ type: 'Products', id }]
        }),
    
        deleteProduct: builder.mutation<void, number>({
          query: (id) => ({
            url: products/${id},
            method: 'DELETE'
          }),
          invalidatesTags: ['Products']
        })
      })
    });
    
    export const {
      useGetProductsQuery,
      useGetProductQuery,
      useCreateProductMutation,
      useUpdateProductMutation,
      useDeleteProductMutation
    } = productsApi;
    
    // Ajouter à configureStore
    export const store = configureStore({
      reducer: {
        cart: cartReducer,
        [productsApi.reducerPath]: productsApi.reducer
      },
      middleware: (getDefaultMiddleware) =>
        getDefaultMiddleware().concat(productsApi.middleware)
    });
    

    Utilisation RTK Query

    // src/components/ProductList.tsx
    import {
      useGetProductsQuery,
      useDeleteProductMutation
    } from '@/store/api/productsApi';
    
    function ProductList() {
      const { data: products, isLoading, error } = useGetProductsQuery();
      const [deleteProduct, { isLoading: isDeleting }] = useDeleteProductMutation();
    
      if (isLoading) return <div>Chargement...</div>;
      if (error) return <div>Erreur</div>;
    
      return (
        <div>
          {products?.map(product => (
            <div key={product.id}>
              ### {product.name}
              <button
                onClick={() => deleteProduct(product.id)}
                disabled={isDeleting}
              >
                Supprimer
              </button>
            </div>
          ))}
        </div>
      );
    }
    
    // Préchargement optimiste
    function ProductCard({ productId }: { productId: number }) {
      const { data: product } = useGetProductQuery(productId);
    
      return <div>{product?.name}</div>;
    }
    

    Redux Persist

    // src/store/index.ts
    import { configureStore } from '@reduxjs/toolkit';
    import {
      persistStore,
      persistReducer,
      FLUSH,
      REHYDRATE,
      PAUSE,
      PERSIST,
      PURGE,
      REGISTER
    } from 'redux-persist';
    import storage from 'redux-persist/lib/storage';
    import cartReducer from './slices/cartSlice';
    
    const persistConfig = {
      key: 'root',
      storage,
      whitelist: ['cart'] // Seulement persister le cart
    };
    
    const persistedCartReducer = persistReducer(persistConfig, cartReducer);
    
    export const store = configureStore({
      reducer: {
        cart: persistedCartReducer,
        user: userReducer
      },
      middleware: (getDefaultMiddleware) =>
        getDefaultMiddleware({
          serializableCheck: {
            ignoredActions: [FLUSH, REHYDRATE, PAUSE, PERSIST, PURGE, REGISTER]
          }
        })
    });
    
    export const persistor = persistStore(store);
    
    // src/App.tsx
    import { PersistGate } from 'redux-persist/integration/react';
    import { store, persistor } from './store';
    
    function App() {
      return (
        <Provider store={store}>
          <PersistGate loading={null} persistor={persistor}>
            <YourApp />
          </PersistGate>
        </Provider>
      );
    }
    

    Avantages et inconvénients

    Avantages :

  • Standard de l’industrie
  • Écosystème massif
  • DevTools puissants
  • RTK Query (data fetching intégré)
  • Excellente documentation
  • Time-travel debugging
  • Middleware riches
  • Inconvénients :

  • Plus verbeux que Zustand
  • Courbe d’apprentissage
  • Boilerplate (réduit avec RTK)
  • Plus lourd (~12kb)
  • 4. Comparaison et benchmarks

    Taille du bundle

    Solution Taille min+gzip Impact
    Context API 0kb Natif React
    Zustand 1.1kb Minimal
    Redux Toolkit 12kb Moyen
    MobX 16kb Élevé
    Recoil 14kb Moyen

    Performance (1000 re-renders)

    // Benchmark code
    function benchmark() {
      console.time('Context API');
      for (let i = 0; i < 1000; i++) {
        // Update context
      }
      console.timeEnd('Context API'); // ~450ms
    
      console.time('Zustand');
      for (let i = 0; i < 1000; i++) {
        // Update zustand
      }
      console.timeEnd('Zustand'); // ~85ms
    
      console.time('Redux Toolkit');
      for (let i = 0; i < 1000; i++) {
        // Dispatch action
      }
      console.timeEnd('Redux Toolkit'); // ~120ms
    }
    
    Solution Temps (ms) Re-renders
    Context API 450 Tous les consommateurs
    Zustand 85 Seulement si sélecteur change
    Redux Toolkit 120 Avec selectors optimisés

    Cas d’usage recommandés

    // ✅ Context API : Pour
    // - Thème
    // - Internationalisation (i18n)
    // - Authentification (simple)
    // - État UI partagé simple
    
    // ✅ Zustand : Pour
    // - Applications petites à moyennes
    // - État global simple
    // - Performance critique
    // - Besoin de simplicité
    
    // ✅ Redux Toolkit : Pour
    // - Applications grandes/complexes
    // - État global complexe
    // - Data fetching centralisé (RTK Query)
    // - Besoin de DevTools avancés
    // - Middleware custom
    // - Time-travel debugging
    

    5. Migration entre solutions

    Context API → Zustand

    // Avant (Context API)
    const CartContext = createContext<CartContextType | undefined>(undefined);
    
    export function CartProvider({ children }: { children: ReactNode }) {
      const [items, setItems] = useState<CartItem[]>([]);
      // ...
      return <CartContext.Provider value={value}>{children}</CartContext.Provider>;
    }
    
    // Après (Zustand)
    export const useCartStore = create<CartStore>((set) => ({
      items: [],
      addItem: (item) => set((state) => ({ /* ... */ }))
    }));
    
    // Migration du code
    // Avant
    const { items, addItem } = useCart();
    
    // Après
    const { items, addItem } = useCartStore();
    // Ou avec sélecteur
    const items = useCartStore(state => state.items);
    const addItem = useCartStore(state => state.addItem);
    

    Zustand → Redux Toolkit

    // Avant (Zustand)
    export const useCartStore = create<CartStore>((set) => ({
      items: [],
      addItem: (item) => set((state) => ({ /* ... */ }))
    }));
    
    // Après (Redux Toolkit)
    const cartSlice = createSlice({
      name: 'cart',
      initialState: { items: [] },
      reducers: {
        addItem: (state, action) => {
          // ...
        }
      }
    });
    
    // Migration du code
    // Avant
    const addItem = useCartStore(state => state.addItem);
    
    // Après
    const dispatch = useAppDispatch();
    dispatch(addItem(product));
    

    6. Tests

    Tester Context API

    // CartContext.test.tsx
    import { render, screen, fireEvent } from '@testing-library/react';
    import { CartProvider, useCart } from './CartContext';
    
    function TestComponent() {
      const { items, addItem } = useCart();
      return (
        <div>
          <div data-testid="count">{items.length}</div>
          <button onClick={() => addItem({ id: 1, name: 'Test', price: 10 })}>
            Add
          </button>
        </div>
      );
    }
    
    describe('CartContext', () => {
      it('adds items to cart', () => {
        render(
          <CartProvider>
            <TestComponent />
          </CartProvider>
        );
    
        expect(screen.getByTestId('count')).toHaveTextContent('0');
    
        fireEvent.click(screen.getByText('Add'));
    
        expect(screen.getByTestId('count')).toHaveTextContent('1');
      });
    });
    

    Tester Zustand

    // cartStore.test.ts
    import { renderHook, act } from '@testing-library/react';
    import { useCartStore } from './cartStore';
    
    describe('cartStore', () => {
      beforeEach(() => {
        useCartStore.setState({ items: [] });
      });
    
      it('adds item to cart', () => {
        const { result } = renderHook(() => useCartStore());
    
        act(() => {
          result.current.addItem({ id: 1, name: 'Test', price: 10 });
        });
    
        expect(result.current.items).toHaveLength(1);
        expect(result.current.items[0].name).toBe('Test');
      });
    });
    

    Tester Redux Toolkit

    // cartSlice.test.ts
    import cartReducer, { addItem, removeItem } from './cartSlice';
    
    describe('cartSlice', () => {
      const initialState = { items: [], loading: false, error: null };
    
      it('adds item', () => {
        const item = { id: 1, name: 'Test', price: 10 };
        const state = cartReducer(initialState, addItem(item));
    
        expect(state.items).toHaveLength(1);
        expect(state.items[0].quantity).toBe(1);
      });
    
      it('removes item', () => {
        const state = {
          items: [{ id: 1, name: 'Test', price: 10, quantity: 1 }],
          loading: false,
          error: null
        };
    
        const newState = cartReducer(state, removeItem(1));
    
        expect(newState.items).toHaveLength(0);
      });
    });
    

    Recommandations 2025

    Choix par taille de projet

    // Petit projet (< 10 composants)
    // → Context API
    
    // Projet moyen (10-50 composants)
    // → Zustand
    
    // Grand projet (> 50 composants)
    // → Redux Toolkit
    
    // Projet avec beaucoup de data fetching
    // → Redux Toolkit + RTK Query
    // ou TanStack Query + Zustand
    

    Combinaisons recommandées

    // Context API (auth) + Zustand (cart)
    const App = () => (
      <AuthProvider>
        <YourApp />
      </AuthProvider>
    );
    
    // Redux Toolkit + TanStack Query
    // Redux pour l'état global
    // TanStack Query pour le data fetching
    
    // Zustand + TanStack Query (moderne et léger)
    export const useCartStore = create(...);
    export const useProducts = () => useQuery(['products'], fetchProducts);
    

    Conclusion

    Chaque solution a ses forces :

  • Context API : Simple, natif, parfait pour des états simples
  • Zustand : Léger, performant, excellent développeur experience
  • Redux Toolkit : Robuste, scalable, standard de l’industrie
  • Choisissez selon

  • Taille du projet
  • Complexité de l’état
  • Besoins en DevTools
  • Expérience de l’équipe
  • Performance requise
  • Ressources

  • Redux Toolkit Documentation
  • Zustand Documentation
  • React Context Documentation
  • RTK Query
  • À propos de l'auteur

    Pascal

    Pascal — SRE & Observability Engineer. J'applique les pratiques SRE (SLI/SLO, alerting, observabilité) à mon homelab Proxmox, avec les mêmes exigences qu'en production.

    Une remarque, un retour ?

    Cet article est vivant - corrections, contre-arguments et retours de production sont les bienvenus. Trois canaux, choisis celui qui te convient.

    Laisser un commentaire