Store
Introduction
Redux Toolkit is the standard way to write Redux. This reference targets Redux Toolkit 2.x and React-Redux 9.x, covering slices, thunks, RTK Query, and hooks.
- Redux Toolkit documentation (redux.js.org)
- GitHub repository (github.com)
Install
npm install @reduxjs/toolkit react-redux
@reduxjs/toolkit bundles Redux, Immer, and Reselect; react-redux provides the bindings.
See: Getting started
Creating a store
import { configureStore } from '@reduxjs/toolkit'
import counterReducer from './counterSlice'
export const store = configureStore({
reducer: {
counter: counterReducer
}
})
configureStore combines slice reducers, adds thunk middleware, and enables DevTools and development checks.
See: configureStore
Middleware
import { configureStore } from '@reduxjs/toolkit'
import { api } from './api'
export const store = configureStore({
reducer: { [api.reducerPath]: api.reducer },
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware().concat(api.middleware)
})
getDefaultMiddleware() keeps the defaults typed; use .concat() or .prepend() rather than spreading.
See: configureStore
Slices
Defining a slice
import { createSlice, type PayloadAction } from '@reduxjs/toolkit'
const counterSlice = createSlice({
name: 'counter',
initialState: { value: 0 },
reducers: {
incremented: (state) => {
state.value += 1
},
added: (state, action: PayloadAction<number>) => {
state.value += action.payload
}
}
})
export const { incremented, added } = counterSlice.actions
export default counterSlice.reducer
createSlice generates the action creators and types. Reducers may mutate state; Immer keeps it immutable.
See: createSlice
Async thunks
import { createAsyncThunk } from '@reduxjs/toolkit'
export const fetchUser = createAsyncThunk(
'users/fetch',
async (id: number, { rejectWithValue }) => {
const res = await fetch(`/api/users/${id}`)
if (!res.ok) return rejectWithValue(await res.text())
return (await res.json()) as User
}
)
The thunk dispatches pending, fulfilled, and rejected actions; rejectWithValue() sets the rejected payload.
See: createAsyncThunk
Handling thunk results
extraReducers: (builder) => {
builder
.addCase(fetchUser.pending, (state) => {
state.status = 'loading'
})
.addCase(fetchUser.fulfilled, (state, action) => {
state.user = action.payload
state.status = 'idle'
})
.addCase(fetchUser.rejected, (state, action) => {
state.error = action.payload as string
})
}
Handle lifecycle actions in extraReducers, one builder.addCase() per action creator.
See: createAsyncThunk
Selectors
import { createSelector } from '@reduxjs/toolkit'
import type { RootState } from './store'
export const selectItems = (state: RootState) => state.cart.items
export const selectTotal = createSelector([selectItems], (items) =>
items.reduce((total, item) => total + item.price, 0)
)
createSelector memoizes derived values, so components skip re-rendering when inputs are unchanged.
See: createSelector
RTK Query
API slice
import {
createApi,
fetchBaseQuery
} from '@reduxjs/toolkit/query/react'
type UserPatch = Partial<User> & { id: number }
export const api = createApi({
reducerPath: 'api',
baseQuery: fetchBaseQuery({ baseUrl: '/api' }),
tagTypes: ['User'],
endpoints: (build) => ({
getUser: build.query<User, number>({
query: (id) => `users/${id}`,
providesTags: ['User']
}),
updateUser: build.mutation<User, UserPatch>({
query: ({ id, ...patch }) => ({
url: `users/${id}`,
method: 'PATCH',
body: patch
}),
invalidatesTags: ['User']
})
})
})
export const { useGetUserQuery, useUpdateUserMutation } = api
One API slice per base URL. tagTypes powers cache invalidation after mutations.
See: RTK Query overview
Adding to the store
import { configureStore } from '@reduxjs/toolkit'
import { api } from './api'
export const store = configureStore({
reducer: { [api.reducerPath]: api.reducer },
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware().concat(api.middleware)
})
Both the reducer and the middleware are required; the middleware drives caching, invalidation, and polling.
See: RTK Query overview
Query hooks
import { useGetUserQuery } from './api'
function User ({ id }: { id: number }) {
const { data, isLoading, error } = useGetUserQuery(id)
if (isLoading) return <p>Loading…</p>
if (error) return <p>Failed</p>
return <p>{data?.name}</p>
}
Hooks fetch on mount, refetch when the argument changes, and share one cache entry per argument.
See: RTK Query overview
React
Provider
import { Provider } from 'react-redux'
import { createRoot } from 'react-dom/client'
import { store } from './store'
createRoot(document.getElementById('root')!).render(
<Provider store={store}>
<App />
</Provider>
)
<Provider> supplies the store to useSelector and useDispatch. Wrap the app once, at the root.
See: Provider
Typed hooks
import { useDispatch, useSelector } from 'react-redux'
import type { RootState, AppDispatch } from './store'
export const useAppDispatch = useDispatch.withTypes<AppDispatch>()
export const useAppSelector = useSelector.withTypes<RootState>()
withTypes is a no-op at runtime; it types both hooks for the whole app.
Reading state
import { useAppDispatch, useAppSelector } from './store'
import { incremented } from './counterSlice'
function Counter () {
const value = useAppSelector((state) => state.counter.value)
const dispatch = useAppDispatch()
return (
<button onClick={() => dispatch(incremented())}>{value}</button>
)
}
Select the smallest value you need; useSelector re-renders when the returned reference changes.
See: Hooks
Legacy Redux
createStore
import { legacy_createStore as createStore } from 'redux'
const store = createStore(counter, { value: 0 })
createStore is deprecated: it renders with a strikethrough but still works. Use configureStore in new code.
See: createStore
connect
import { connect } from 'react-redux'
export default connect(
(state) => ({ message: state.message }),
(dispatch) => ({
onMessageClick: (message) => dispatch({ type: 'click', message })
})
)(App)
connect is deprecated as of React-Redux 9.3.0. It still works, but the hooks above are simpler and type better.
See: connect
References
- Redux Toolkit docs (redux.js.org)
- Migrating to modern Redux (redux.js.org)
- Redux Toolkit (npmjs.com)
- React Redux (npmjs.com)
0 Comments for this cheatsheet. Write yours!