O que é Redux Toolkit?
Redux é um container de estado previsível: um único store armazena o estado da sua aplicação, e a única maneira de alterá-lo é disparando (dispatch) uma action que é processada por um reducer puro. Essa restrição é o que torna o Redux poderoso — cada mudança de estado é explícita, rastreável e testável.
Por anos, o Redux também foi famoso pelo excesso de boilerplate. O Redux Toolkit resolveu isso. Ele é a maneira oficial e recomendada de escrever Redux hoje em dia, reunindo configureStore, createSlice, Immer para atualizações imutáveis, thunk middleware e devtools em um único pacote. Você obtém a arquitetura sem a burocracia.
A store e os slices
Um slice reúne a lógica do reducer e as actions de uma funcionalidade em um só lugar. configureStore combina os slices na 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 gera os action creators e o reducer para você, eliminando a necessidade de escrever constantes de tipos de action manualmente. O Immer alimenta os reducers, o que significa que você pode escrever códigos que parecem mutações, enquanto o estado permanece imutável.
// 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;
Leitura e despacho
Os componentes leem o estado com useSelector e enviam ações com 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>
);
}
Selecione a menor fatia (slice) necessária. O useSelector renderiza novamente o componente quando o valor selecionado muda, portanto, selecionar todo o estado anula a otimização. Use createSelector do Reselect quando um valor derivado precisaria ser recalculado a cada renderização.
Lógica assíncrona com thunks
createAsyncThunk gera os três tipos de action que uma requisição assíncrona produz, os quais você manipula no 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;
});
},
});
Este é o padrão explícito do Redux: loading, success e failure são todas actions que você pode inspecionar no devtools.
RTK Query para estado do servidor
Para a busca de dados, o RTK Query remove completamente a necessidade de thunks. Você define um API slice com endpoints, e ele gerencia o cache, a deduplicação, a invalidação e o 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();
O RTK Query é a resposta do Redux ao TanStack Query: mantenha o estado do servidor em um cache com ciclo de vida e mantenha o estado do cliente em slices. Misturar essas duas responsabilidades em um único lugar é o que torna as aplicações Redux confusas.
Middleware e devtools
configureStore habilita a extensão Redux DevTools e inclui o middleware thunk por padrão. Você pode adicionar seus próprios middlewares para logging, analytics ou side effects.
// 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),
});
Os devtools permitem inspecionar cada action disparada, comparar a diferença do estado antes e depois, e reproduzir o histórico — o principal motivo pelo qual as equipes escolhem Redux para estados complexos.
Melhores práticas
- Use Redux Toolkit, nunca Redux escrito manualmente.
- Mantenha um slice por funcionalidade e agrupe a lógica relacionada.
- Selecione a menor fatia de estado possível em
useSelector. - Use
createSelectorpara dados derivados que sejam caros para computar. - Mantenha o estado do servidor no RTK Query, não em slices.
- Armazene apenas dados serializáveis; mantenha valores não serializáveis fora do store.
- Modele as actions como eventos que aconteceram, não como setters.
Erros comuns
- Selecionar todo o estado e disparar a re-renderização a cada alteração.
- Colocar dados do servidor em slices e gerenciar flags de carregamento manualmente.
- Armazenar valores não serializáveis, como instâncias de classes ou funções.
- Mutar o estado fora de um reducer.
- Usar Redux para estados locais de componentes que
useStateresolveria. - Escrever Redux legado e verboso em vez de utilizar slices.
Próximos passos
O Redux Toolkit é a escolha madura para estados de cliente grandes e complexos. Compare-o com a Context API nativa do React e com o mais leve Zustand, e separe as preocupações de servidor utilizando TanStack Query ou RTK Query. Para Vue, o papel equivalente é preenchido pelo Pinia.