React 66 ⚛️ Redux Toolkit Setup and Provider
A global state that many components read and write needs a single source of truth, a predictable way to update it, and a way to connect the components to it. Redux provides the pattern, and Redux Toolkit provides the tooling that removes the boilerplate. The configureStore function creates the store, the Provider component makes it available to the tree, and the useSelector and useDispatch hooks connect the components. The setup is small, and the conventions are what make the store predictable.
Key point: The Redux store is a single JavaScript object that holds the entire application state. The store is created once, wrapped around the application with the Provider, and read with the useSelector. The store is not a place for the UI state, the form values, or the component-local values; it is the place for the state that many components share.
Why Redux Toolkit exists
The boilerplate problem. The classic Redux required the action types, the action creators, the reducers, the switch statements, and the immutable updates by hand. The Redux Toolkit reduces the pattern to the createSlice, which generates the action creators and the reducer from the one object.
The immutability problem. The classic Redux required the spread operators to update the nested state. The mistake with the spread produced the state bug. The Redux Toolkit uses the Immer library, which lets the developer write the “mutation” code, and the library produces the immutable update.
The store setup problem. The classic store setup required the applyMiddleware, the compose, and the createStore. The configureStore combines the reducers, the middleware, and the DevTools into the one call.
The DevTools problem. The Redux DevTools are the standard, and the classic setup required the manual wiring. The configureStore enables them by default.
The async problem. The async logic required the redux-thunk or the redux-saga. The Redux Toolkit includes the createAsyncThunk and the listener middleware, and the createApi covers the data fetching.
The type problem. The TypeScript setup required the manual types. The Redux Toolkit generates the types from the slices, and the useSelector and the useDispatch are typed from the store.
The structure problem. The Redux Toolkit provides the conventions: the slice per domain, the features folder, the typed hooks. The structure is the documentation.
a. The store creation
The configureStore function creates the store. It takes the reducer object and the optional middleware, DevTools, and preloaded state.
The basic store
// app/store.js
import { configureStore } from '@reduxjs/toolkit';
import todosReducer from '../features/todos/todosSlice';
export const store = configureStore({
reducer: {
todos: todosReducer,
},
});
The reducer object maps the state keys to the reducers. The todos key becomes the state.todos in the store.
The store with the middleware
import { configureStore } from '@reduxjs/toolkit';
import todosReducer from '../features/todos/todosSlice';
import logger from './logger';
export const store = configureStore({
reducer: {
todos: todosReducer,
},
middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat(logger),
});
The getDefaultMiddleware returns the default middleware, and the concat adds the custom. The default middleware includes the thunk and the immutability check.
The store with the DevTools
export const store = configureStore({
reducer: { todos: todosReducer },
devTools: process.env.NODE_ENV !== 'production',
});
The devTools option enables the Redux DevTools. The default is the enabled in the development.
The preloaded state
const preloadedState = {
todos: [{ id: 1, text: 'Learn Redux', completed: false }],
};
export const store = configureStore({
reducer: { todos: todosReducer },
preloadedState,
});
The preloadedState seeds the store. The pattern is used for the SSR and the tests.
The store methods
| Method | Purpose |
|---|---|
getState() | Return the current state |
dispatch(action) | Dispatch the action |
subscribe(listener) | Subscribe to the changes |
replaceReducer(next) | Replace the reducer |
The subscribe returns the unsubscribe function. The React-Redux does the subscription for the components.
The store setup table
| Option | Purpose |
|---|---|
reducer | The reducer object or the function |
middleware | The middleware array |
devTools | The DevTools enable |
preloadedState | The initial state |
enhancers | The store enhancers |
b. The Provider
The Provider component makes the store available to the React tree. The useSelector and the useDispatch hooks read the store from the context.
The basic provider
import React from 'react';
import ReactDOM from 'react-dom/client';
import { Provider } from 'react-redux';
import { store } from './app/store';
import App from './App';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<Provider store={store}>
<App />
</Provider>
);
The Provider wraps the root component, and the store is available to the entire tree.
The typed hooks
// app/hooks.js
import { useDispatch, useSelector } from 'react-redux';
export const useAppDispatch = () => useDispatch();
export const useAppSelector = useSelector;
The typed hooks are the convention. The useAppDispatch returns the typed dispatch, and the useAppSelector is the typed selector.
The typed hooks with the TypeScript
// app/hooks.ts
import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux';
import type { RootState, AppDispatch } from './store';
export const useAppDispatch = () => useDispatch<AppDispatch>();
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;
The RootState and the AppDispatch are the inferred types from the store.
The store types
// app/store.ts
import { configureStore } from '@reduxjs/toolkit';
import todosReducer from '../features/todos/todosSlice';
export const store = configureStore({
reducer: {
todos: todosReducer,
},
});
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;
The RootState is the entire state tree, and the AppDispatch is the dispatch type.
The provider table
| Element | Purpose |
|---|---|
Provider | The store context |
useSelector | The read |
useDispatch | The dispatch |
| The typed hooks | The TypeScript |
The RootState | The state type |
The AppDispatch | The dispatch type |
c. The slice
The slice is the collection of the reducer, the action creators, and the initial state. The createSlice generates the three from the one object.
The basic slice
// features/todos/todosSlice.js
import { createSlice } from '@reduxjs/toolkit';
const initialState = {
items: [],
filter: 'all',
};
const todosSlice = createSlice({
name: 'todos',
initialState,
reducers: {
addTodo: (state, action) => {
state.items.push({ id: Date.now(), text: action.payload, completed: false });
},
toggleTodo: (state, action) => {
const todo = state.items.find((t) => t.id === action.payload);
if (todo) todo.completed = !todo.completed;
},
removeTodo: (state, action) => {
state.items = state.items.filter((t) => t.id !== action.payload);
},
},
});
export const { addTodo, toggleTodo, removeTodo } = todosSlice.actions;
export default todosSlice.reducer;
The name is the prefix for the action types, the initialState is the state, and the reducers is the map of the action handlers.
The Immer and the “mutation”
The state.items.push(...) looks like the mutation, and the Immer produces the immutable update. The state.items = state.items.filter(...) is the assignment, and the Immer handles it.
The action creators
The todosSlice.actions holds the action creators. The addTodo('text') returns the { type: 'todos/addTodo', payload: 'text' }.
The extra reducers
const todosSlice = createSlice({
name: 'todos',
initialState,
reducers: { ... },
extraReducers: (builder) => {
builder
.addCase(fetchTodos.pending, (state) => {
state.status = 'loading';
})
.addCase(fetchTodos.fulfilled, (state, action) => {
state.status = 'succeeded';
state.items = action.payload;
})
.addCase(fetchTodos.rejected, (state, action) => {
state.status = 'failed';
state.error = action.error.message;
});
},
});
The extraReducers handles the actions from the other slices and the async thunks.
The slice table
| Element | Purpose |
|---|---|
name | The action type prefix |
initialState | The initial state |
reducers | The action handlers |
extraReducers | The external handlers |
.actions | The action creators |
.reducer | The combined reducer |
Complete Example Session
// ============================================
// PART 1: THE SLICE
// ============================================
import { createSlice } from '@reduxjs/toolkit';
const todosSlice = createSlice({
name: 'todos',
initialState: { items: [] },
reducers: {
addTodo: (state, action) => {
state.items.push({ id: Date.now(), text: action.payload });
},
removeTodo: (state, action) => {
state.items = state.items.filter((t) => t.id !== action.payload);
},
},
});
export const { addTodo, removeTodo } = todosSlice.actions;
export default todosSlice.reducer;
// ============================================
// PART 2: THE STORE
// ============================================
import { configureStore } from '@reduxjs/toolkit';
import todosReducer from '../features/todos/todosSlice';
export const store = configureStore({
reducer: {
todos: todosReducer,
},
});
// ============================================
// PART 3: THE PROVIDER
// ============================================
import { Provider } from 'react-redux';
import { store } from './app/store';
root.render(
<Provider store={store}>
<App />
</Provider>
);
// ============================================
// PART 4: THE TYPED HOOKS
// ============================================
import { useDispatch, useSelector } from 'react-redux';
export const useAppDispatch = () => useDispatch();
export const useAppSelector = useSelector;
// ============================================
// PART 5: THE COMPONENT
// ============================================
import { useAppDispatch, useAppSelector } from '../app/hooks';
import { addTodo, removeTodo } from '../features/todos/todosSlice';
function TodoList() {
const todos = useAppSelector((state) => state.todos.items);
const dispatch = useAppDispatch();
return (
<ul>
{todos.map((todo) => (
<li key={todo.id}>
{todo.text}
<button onClick={() => dispatch(removeTodo(todo.id))}>Remove</button>
</li>
))}
<button onClick={() => dispatch(addTodo('New todo'))}>Add</button>
</ul>
);
}
// ============================================
// PART 6: THE TYPESCRIPT STORE
// ============================================
import { configureStore } from '@reduxjs/toolkit';
import todosReducer from '../features/todos/todosSlice';
export const store = configureStore({
reducer: {
todos: todosReducer,
},
});
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;
// ============================================
// PART 7: THE TYPESCRIPT HOOKS
// ============================================
import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux';
import type { RootState, AppDispatch } from './store';
export const useAppDispatch = () => useDispatch<AppDispatch>();
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;
// ============================================
// PART 8: THE MIDDLEWARE
// ============================================
export const store = configureStore({
reducer: { todos: todosReducer },
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware().concat(myLogger),
});
// ============================================
// PART 9: THE PRELOADED STATE
// ============================================
export const store = configureStore({
reducer: { todos: todosReducer },
preloadedState: {
todos: { items: [{ id: 1, text: 'Initial' }] },
},
});
// ============================================
// PART 10: THE COMPLETE PATTERN
// ============================================
// 1. Create the slice with the createSlice.
// 2. Create the store with the configureStore.
// 3. Wrap the app with the Provider.
// 4. Create the typed hooks.
// 5. Use the useSelector to read the state.
// 6. Use the useDispatch to dispatch the actions.
// 7. Use the extraReducers for the external actions.
// 8. Use the preloadedState for the SSR and the tests.
// 9. Use the middleware for the logging and the side effects.
// 10. Enable the DevTools in the development.
The ten parts showed the slice, the store, the provider, the typed hooks, the component, the TypeScript store, the TypeScript hooks, the middleware, the preloaded state, and the complete pattern.
Quick Reference
The configureStore Options
| Option | Purpose |
|---|---|
reducer | The reducer object |
middleware | The middleware array |
devTools | The DevTools enable |
preloadedState | The initial state |
enhancers | The store enhancers |
The Slice
| Element | Purpose |
|---|---|
name | The action type prefix |
initialState | The initial state |
reducers | The action handlers |
extraReducers | The external handlers |
.actions | The action creators |
.reducer | The reducer |
The React-Redux Hooks
| Hook | Purpose |
|---|---|
useSelector | The read the state |
useDispatch | The dispatch the action |
useStore | The store reference |
useAppSelector | The typed selector |
useAppDispatch | The typed dispatch |
The Store Methods
| Method | Purpose |
|---|---|
getState() | The current state |
dispatch(action) | The dispatch |
subscribe(listener) | The subscribe |
replaceReducer(next) | The replace |
The Types
| Type | Purpose |
|---|---|
RootState | The state tree type |
AppDispatch | The dispatch type |
TypedUseSelectorHook | The typed selector |
Best Practices
✅ Do This:
// Use the createSlice for the reducers.
const todosSlice = createSlice({ name: 'todos', ... }); // ✅
// Use the configureStore for the store.
export const store = configureStore({ reducer: { todos: todosReducer } }); // ✅
// Wrap the app with the Provider.
<Provider store={store}><App /></Provider> // ✅
// Use the typed hooks.
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector; // ✅
// Use the selector for the read.
const todos = useAppSelector((state) => state.todos.items); // ✅
// Use the dispatch for the write.
dispatch(addTodo('text')); // ✅
// Use the extraReducers for the thunks.
extraReducers: (builder) => builder.addCase(...) // ✅
// Use the Immer "mutation".
state.items.push(todo); // ✅
❌ Don’t Do This:
// Don't use the classic createStore.
import { createStore } from 'redux'; // ❌ Use the configureStore
// Don't mutate the state outside the slice.
state.todos.items.push(todo); // ❌ The Immer is in the slice
// Don't put the UI state in the store.
store.dispatch(setModalOpen(true)); // ⚠️ The local state
// Don't forget the Provider.
<App /> // ❌ The useSelector throws
// Don't use the untyped hooks in the TypeScript.
const todos = useSelector((state) => state.todos); // ⚠️ The state is any
// Don't select the whole state.
const state = useAppSelector((state) => state); // ❌ The re-render on any change
// Don't forget the key in the reducer object.
configureStore({ reducer: todosReducer }); // ⚠️ The single reducer
Common Pitfalls
| Pitfall | Why It Happens | Fix |
|---|---|---|
The useSelector throws | No Provider | Wrap the app |
| The component re-renders | The broad selector | Select the slice |
| The state not updated | The mutation outside the slice | Use the slice |
| The type errors | The untyped hooks | Use the typed hooks |
| The action not dispatched | The wrong import | Import from the slice |
| The middleware missing | The override | Use getDefaultMiddleware |
| The DevTools disabled | The production build | The expected |
Real-World Examples
1. The Slice
const todosSlice = createSlice({ name: 'todos', initialState, reducers: { ... } });
2. The Store
export const store = configureStore({ reducer: { todos: todosReducer } });
3. The Provider
<Provider store={store}><App /></Provider>
4. The Typed Selector
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;
5. The Typed Dispatch
export const useAppDispatch = () => useDispatch<AppDispatch>();
6. The Read
const todos = useAppSelector((state) => state.todos.items);
7. The Write
dispatch(addTodo('text'));
8. The extraReducers
extraReducers: (builder) => builder.addCase(fetchTodos.fulfilled, ...)
9. The Middleware
middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat(logger)
10. The Complete Pattern
<Provider store={store}><App /></Provider>
Visual
The Redux Flow
┌────────────────────────────────────────────────────────────┐
│ THE REDUX FLOW │
│ │
│ ┌──────────────┐ │
│ │ Component │ │
│ │ dispatch │ │
│ └──────┬───────┘ │
│ │ │
│ v │
│ ┌──────────────┐ │
│ │ Action │ │
│ │ { type, │ │
│ │ payload } │ │
│ └──────┬───────┘ │
│ │ │
│ v │
│ ┌──────────────┐ │
│ │ Reducer │ │
│ │ (the slice) │ │
│ └──────┬───────┘ │
│ │ │
│ v │
│ ┌──────────────┐ │
│ │ Store │ │
│ │ (the new │ │
│ │ state) │ │
│ └──────┬───────┘ │
│ │ │
│ v │
│ ┌──────────────┐ │
│ │ Component │ │
│ │ useSelector │ │
│ │ (re-render) │ │
│ └──────────────┘ │
└────────────────────────────────────────────────────────────┘
The Provider
┌────────────────────────────────────────────────────────────┐
│ THE PROVIDER │
│ │
│ ┌──────────────────────────────────────────────────┐ │
│ │ <Provider store={store}> │ │
│ │ <App> │ │
│ │ <Header /> │ │
│ │ <TodoList /> │ │
│ │ <Footer /> │ │
│ │ </App> │ │
│ │ </Provider> │ │
│ └──────────────────────────────────────────────────┘ │
│ │
│ The Provider puts the store in the React context. │
│ Every descendant can read the store with the useSelector. │
└────────────────────────────────────────────────────────────┘
The Slice
┌────────────────────────────────────────────────────────────┐
│ THE SLICE │
│ │
│ createSlice({ │
│ name: 'todos', │
│ initialState: { items: [] }, │
│ reducers: { │
│ addTodo: (state, action) => { │
│ state.items.push(action.payload); │
│ }, │
│ }, │
│ }); │
│ │
│ ┌──────────────────────────────────────────────────┐ │
│ │ name: the prefix 'todos' │ │
│ │ reducers: the map of the handlers │ │
│ │ .actions: the creators │ │
│ │ .reducer: the reducer │ │
│ └──────────────────────────────────────────────────┘ │
│ │
│ The Immer lets the "mutation" code produce the immutable. │
└────────────────────────────────────────────────────────────┘
The Typed Hooks
┌────────────────────────────────────────────────────────────┐
│ THE TYPED HOOKS │
│ │
│ // app/store.ts │
│ export type RootState = ReturnType<typeof store.getState>;│
│ export type AppDispatch = typeof store.dispatch; │
│ │
│ // app/hooks.ts │
│ export const useAppDispatch = () => useDispatch<AppDispatch>();
│ export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;
│ │
│ ┌──────────────────────────────────────────────────┐ │
│ │ useAppSelector((state) => state.todos) │ │
│ │ → the state is typed as the RootState │ │
│ │ │ │
│ │ useAppDispatch() │ │
│ │ → the dispatch is typed as the AppDispatch │ │
│ └──────────────────────────────────────────────────┘ │
└────────────────────────────────────────────────────────────┘
Summary
| Item | Value |
|---|---|
configureStore | Create the store |
Provider | Make the store available |
useSelector | Read the state |
useDispatch | Dispatch the action |
createSlice | Create the reducer and the actions |
extraReducers | The external handlers |
RootState | The state type |
AppDispatch | The dispatch type |
| The typed hooks | The TypeScript |
| The Immer | The immutable updates |
Key takeaways:
- The
configureStorecreates the store. It takes the reducer object, and the keys become the state slices. The middleware, the DevTools, and the preloaded state are the options. - The
Providermakes the store available to the tree. Every descendant reads the store with theuseSelectorand dispatches with theuseDispatch. TheProvideris the single wrap at the root. - The
createSlicegenerates the reducer and the action creators. Thenameis the prefix, theinitialStateis the state, and thereducersis the map of the handlers. The.actionsand the.reducerare the exports. - The Immer lets the “mutation” code produce the immutable update. The
state.items.push(...)looks like the mutation, and the library produces the new state. The developer writes the readable code, and the store is the immutable. - The typed hooks are the TypeScript convention. The
useAppSelectorand theuseAppDispatchare the typed from theRootStateand theAppDispatch. TheuseSelectorand theuseDispatchare the untyped. - The
extraReducershandles the external actions. The async thunks and the other slices’ actions are the cases. Thebuilder.addCaseis the registration. - The store is the global state, not the UI state. The form values, the modal open, and the local values are the component state. The store is the state that many components share.
- The selectors should be narrow. The
useAppSelector((state) => state.todos.items)selects the slice. The broad(state) => statere-renders the component on every change.
Remember: The Redux Toolkit setup is the configureStore, the Provider, and the createSlice. The store is created once, wrapped at the root, and read with the useSelector and written with the useDispatch. The slice generates the reducer and the actions, and the Immer produces the immutable updates. The result is the global state that is predictable, the components that are connected, and the boilerplate that is minimal.
Stop using slow, ad-bloated tool sites! 🤮
🔎 Search “KandZ Tools” on Google to use many professional utilities for free.
KandZ.me is the ultimate minimalist hub for:
✅ Finance (Mortgage, Interest, Inflation)
✅ Tech (Base64, JSON, Dev Suite, IP)
✅ Health (BMI, BMR, TDEE)
✅ Productivity (Timer, Workspace, QR)
⚡️ Fast & Private
🔒 No data leaves your device
💎 100% Free
🔗 Use it now: https://tools.kandz.me
🔖 Bookmark it—you’ll need it later!