Qu’est-ce que Redux Toolkit ?
Redux est un conteneur d’état prévisible : un store unique détient l’état de votre application, et le seul moyen de le modifier est de dispatcher une action traitée par un reducer pur. C’est cette contrainte qui rend Redux puissant — chaque changement d’état est explicite, traçable et testable.
Pendant des années, Redux a également été célèbre pour son code répétitif (boilerplate). Redux Toolkit a corrigé cela. C’est aujourd’hui la méthode officielle et recommandée pour écrire du Redux, regroupant configureStore, createSlice, Immer pour les mises à jour immuables, le middleware thunk et les devtools dans un seul package. Vous bénéficiez ainsi de l’architecture sans la lourdeur administrative.
Le store et les slices
Une slice regroupe la logique du reducer et les actions d’une fonctionnalité en un seul endroit. configureStore combine les slices dans le store.
// app/store.js
import { configureStore } from "@reduxjs/toolkit";
import counterReducer from "../features/counter/counterSlice";
import todosReducer from "../features/todos/todosSlice";
export const store = configureStore({
reducer: {
counter: counterReducer,
todos: todosReducer,
},
});
// main.jsx
import { Provider } from "react-redux";
import { store } from "./app/store";
<Provider store={store}>
<App />
</Provider>;
createSlice génère pour vous les action creators et le reducer, vous n’avez donc jamais besoin d’écrire manuellement des constantes de type d’action. Immer propulse les reducers, ce qui signifie que vous pouvez écrire du code qui ressemble à une mutation alors que l’état reste immuable.
// features/todos/todosSlice.js
import { createSlice } from "@reduxjs/toolkit";
const todosSlice = createSlice({
name: "todos",
initialState: [],
reducers: {
add(state, action) {
state.push(action.payload);
},
toggle(state, action) {
const todo = state.find((t) => t.id === action.payload);
if (todo) todo.done = !todo.done;
},
remove(state, action) {
return state.filter((t) => t.id !== action.payload);
},
},
});
export const { add, toggle, remove } = todosSlice.actions;
export default todosSlice.reducer;
Lecture et dispatch
Les composants lisent l’état avec useSelector et envoient des actions avec useDispatch.
// TodoList.jsx
import { useSelector, useDispatch } from "react-redux";
import { add, toggle } from "../features/todos/todosSlice";
export function TodoList() {
const todos = useSelector((state) => state.todos);
const dispatch = useDispatch();
return (
<ul>
{todos.map((todo) => (
<li key={todo.id} onClick={() => dispatch(toggle(todo.id))}>
{todo.done ? "✓" : "○"} {todo.text}
</li>
))}
<button onClick={() => dispatch(add({ id: 1, text: "New" }))}>
Add
</button>
</ul>
);
}
Sélectionnez la plus petite tranche (slice) possible. useSelector provoque le re-rendu du composant lorsque la valeur sélectionnée change ; sélectionner l’intégralité de l’état annulerait donc l’optimisation. Utilisez createSelector de Reselect lorsqu’une valeur dérivée risquerait d’être recalculée à chaque rendu.
Logique asynchrone avec les thunks
createAsyncThunk génère les trois types d’actions produits par une requête asynchrone, que vous gérez ensuite dans extraReducers.
// features/users/usersSlice.js
import { createSlice, createAsyncThunk } from "@reduxjs/toolkit";
export const fetchUsers = createAsyncThunk("users/fetch", async () => {
const res = await fetch("/api/users");
if (!res.ok) throw new Error("Failed to fetch users");
return res.json();
});
const usersSlice = createSlice({
name: "users",
initialState: { items: [], status: "idle", error: null },
reducers: {},
extraReducers: (builder) => {
builder
.addCase(fetchUsers.pending, (state) => {
state.status = "loading";
})
.addCase(fetchUsers.fulfilled, (state, action) => {
state.status = "succeeded";
state.items = action.payload;
})
.addCase(fetchUsers.rejected, (state, action) => {
state.status = "failed";
state.error = action.error.message;
});
},
});
C’est le pattern Redux explicite : le chargement (loading), le succès (success) et l’échec (failure) sont tous des actions que vous pouvez inspecter dans les devtools.
RTK Query pour l’état du serveur
Pour la récupération de données, RTK Query supprime complètement les thunks. Vous définissez un slice d’API avec des endpoints, et il gère le caching, la déduplication, l’invalidation et le refetching.
// features/api/apiSlice.js
import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react";
export const api = createApi({
reducerPath: "api",
baseQuery: fetchBaseQuery({ baseUrl: "/api" }),
tagTypes: ["Post"],
endpoints: (builder) => ({
getPosts: builder.query({ query: () => "posts", providesTags: ["Post"] }),
addPost: builder.mutation({
query: (body) => ({ url: "posts", method: "POST", body }),
invalidatesTags: ["Post"],
}),
}),
});
export const { useGetPostsQuery, useAddPostMutation } = api;
// Posts.jsx
const { data, isLoading, error } = useGetPostsQuery();
RTK Query est la réponse de Redux à TanStack Query : conserver l’état du serveur dans un cache avec un cycle de vie, et conserver l’état du client dans des slices. C’est en mélangeant ces deux responsabilités au même endroit que les applications Redux deviennent confuses.
Middleware et devtools
configureStore active l’extension Redux DevTools et inclut le middleware thunk par défaut. Vous pouvez ajouter vos propres middlewares pour le logging, l’analyse ou les effets de bord.
// middleware.js
const logger = (store) => (next) => (action) => {
console.log("dispatching", action.type);
return next(action);
};
export const store = configureStore({
reducer: rootReducer,
middleware: (getDefault) => getDefault().concat(logger),
});
Les devtools vous permettent d’inspecter chaque action dispatchée, de comparer l’état avant et après, et de rejouer l’historique — c’est la raison principale pour laquelle les équipes choisissent Redux pour la gestion d’états complexes.
Bonnes pratiques
- Utilisez Redux Toolkit, ne rédigez jamais Redux à la main.
- Maintenez un seul slice par fonctionnalité et regroupez la logique associée.
- Sélectionnez la plus petite partie possible de l’état dans
useSelector. - Utilisez
createSelectorpour les données dérivées dont le calcul est coûteux. - Gérez l’état du serveur avec RTK Query, et non dans des slices.
- Ne stockez que des données sérialisables ; gardez les valeurs non sérialisables en dehors du store.
- Modélisez les actions comme des événements qui se sont produits, et non comme des setters.
Erreurs courantes
- Sélectionner l’intégralité du state et déclencher un re-render à chaque modification.
- Placer des données serveur dans des slices et gérer manuellement les indicateurs de chargement (loading flags).
- Stocker des valeurs non sérialisables, comme des instances de classe ou des fonctions.
- Muter le state en dehors d’un reducer.
- Utiliser Redux pour un état local de composant que
useStatepourrait gérer. - Écrire du code Redux legacy verbeux au lieu d’utiliser des slices.
Et après ?
Redux Toolkit est le choix mature pour gérer des états clients larges et complexes. Comparez-le avec l’ Context API native de React et la solution plus légère Zustand, et déléguez la gestion du serveur à TanStack Query ou RTK Query. Pour Vue, c’est Pinia qui remplit ce rôle.