Redux Cheat Sheet
A reference for Redux Toolkit's store setup, slices, reducers, and React bindings for predictable, centralized state management.
Store Setup
Configuring the store and providing it to a React app.
import { configureStore } from '@reduxjs/toolkit';import counterReducer from './counterSlice';export const store = configureStore({ reducer: { counter: counterReducer, },});// wrap the appimport { Provider } from 'react-redux';<Provider store={store}> <App /></Provider>
createSlice
Defining reducers and action creators together with Redux Toolkit.
import { createSlice } from '@reduxjs/toolkit';const counterSlice = createSlice({ name: 'counter', initialState: { value: 0 }, reducers: { increment: (state) => { state.value += 1; // Immer lets you write "mutating" logic safely }, decrement: (state) => { state.value -= 1; }, incrementBy: (state, action) => { state.value += action.payload; }, },});export const { increment, decrement, incrementBy } = counterSlice.actions;export default counterSlice.reducer;
Using Redux in React
Reading state and dispatching actions with react-redux hooks.
import { useSelector, useDispatch } from 'react-redux';import { increment, decrement } from './counterSlice';function Counter() { const count = useSelector((state) => state.counter.value); const dispatch = useDispatch(); return ( <button onClick={() => dispatch(increment())}> Count: {count} </button> );}
Core Concepts
Terminology used throughout the Redux ecosystem.
- Store- the single source of truth holding the entire application state tree
- Action- a plain object with a 'type' field describing what happened
- Reducer- a pure function of the form (state, action) => newState
- configureStore- RTK helper that sets up the store with good defaults (thunk middleware, DevTools)
- createSlice- generates action creators and a reducer from a set of reducer functions
- createAsyncThunk- RTK helper for handling async request lifecycles (pending/fulfilled/rejected)
- Selector- a function that extracts and derives a piece of state from the store
- Middleware- intercepts dispatched actions before they reach the reducer, e.g. redux-thunk
createAsyncThunk & extraReducers
Handling the pending/fulfilled/rejected lifecycle of an async request in a slice.
import { createAsyncThunk, createSlice } from '@reduxjs/toolkit';export const fetchUser = createAsyncThunk( 'user/fetchUser', async (userId, { rejectWithValue }) => { const res = await fetch(`/api/users/${userId}`); if (!res.ok) return rejectWithValue(await res.json()); return res.json(); });const userSlice = createSlice({ name: 'user', initialState: { data: null, status: 'idle', error: null }, reducers: {}, extraReducers: (builder) => { builder .addCase(fetchUser.pending, (state) => { state.status = 'loading'; }) .addCase(fetchUser.fulfilled, (state, action) => { state.status = 'succeeded'; state.data = action.payload; }) .addCase(fetchUser.rejected, (state, action) => { state.status = 'failed'; state.error = action.payload ?? action.error.message; }); }});export default userSlice.reducer;
RTK Query Data Fetching
Declarative API slices with caching, invalidation, and auto-generated hooks.
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;// component.jsxfunction Posts() { const { data, isLoading } = useGetPostsQuery(); const [addPost] = useAddPostMutation(); // ...}
Memoized Selectors with createSelector
Deriving computed data from the store without recalculating unless inputs change.
import { createSelector } from '@reduxjs/toolkit';const selectTodos = (state) => state.todos.items;const selectFilter = (state) => state.todos.filter;export const selectVisibleTodos = createSelector( [selectTodos, selectFilter], (todos, filter) => { switch (filter) { case 'completed': return todos.filter((t) => t.completed); case 'active': return todos.filter((t) => !t.completed); default: return todos; } });// in a componentconst visibleTodos = useSelector(selectVisibleTodos);// result is cached until selectTodos or selectFilter's output changes
Normalized State with createEntityAdapter
Storing collections in a normalized {ids, entities} shape with generated CRUD reducers and selectors.
import { createEntityAdapter, createSlice } from '@reduxjs/toolkit';const todosAdapter = createEntityAdapter({ sortComparer: (a, b) => b.createdAt.localeCompare(a.createdAt)});const todosSlice = createSlice({ name: 'todos', initialState: todosAdapter.getInitialState({ status: 'idle' }), reducers: { todoAdded: todosAdapter.addOne, todoUpdated: todosAdapter.updateOne, todoRemoved: todosAdapter.removeOne, todosReceived: todosAdapter.setAll }});export const { selectAll: selectAllTodos, selectById: selectTodoById, selectIds: selectTodoIds} = todosAdapter.getSelectors((state) => state.todos);export const { todoAdded, todoUpdated, todoRemoved, todosReceived } = todosSlice.actions;export default todosSlice.reducer;
Middleware & Store Enhancers
Concepts for intercepting and extending the dispatch pipeline.
- Middleware signature- store => next => action => {...}; must call next(action) to continue the chain
- redux-thunk- default middleware in RTK's configureStore; lets action creators return functions instead of plain objects
- listenerMiddleware- RTK's typed alternative to sagas/thunks for reacting to specific actions/state changes with startListening
- getDefaultMiddleware- configureStore's built-in middleware (thunk, serializable/immutable state checks); extend rather than replace it
- Store enhancer- a higher-order function that wraps createStore itself, e.g. to add devtools or persistence
- redux-persist- enhancer/middleware combo that serializes store state to storage and rehydrates it on load
- combineReducers- merges multiple slice reducers into one root reducer keyed by state slice name
- RTK Query tags- providesTags/invalidatesTags drive automatic refetching when related mutations occur
createSlice uses Immer internally, so reducers can 'mutate' state directly (state.value += 1) and Immer produces the correct immutable update behind the scenes -- but never mutate state outside of a slice reducer, since that safety net only applies there.