| |

React 66 ⚛️ Redux Toolkit Setup and Provider

⏱️ Estimated read: 13 min | |

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

MethodPurpose
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

OptionPurpose
reducerThe reducer object or the function
middlewareThe middleware array
devToolsThe DevTools enable
preloadedStateThe initial state
enhancersThe 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

ElementPurpose
ProviderThe store context
useSelectorThe read
useDispatchThe dispatch
The typed hooksThe TypeScript
The RootStateThe state type
The AppDispatchThe 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

ElementPurpose
nameThe action type prefix
initialStateThe initial state
reducersThe action handlers
extraReducersThe external handlers
.actionsThe action creators
.reducerThe 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

OptionPurpose
reducerThe reducer object
middlewareThe middleware array
devToolsThe DevTools enable
preloadedStateThe initial state
enhancersThe store enhancers

The Slice

ElementPurpose
nameThe action type prefix
initialStateThe initial state
reducersThe action handlers
extraReducersThe external handlers
.actionsThe action creators
.reducerThe reducer

The React-Redux Hooks

HookPurpose
useSelectorThe read the state
useDispatchThe dispatch the action
useStoreThe store reference
useAppSelectorThe typed selector
useAppDispatchThe typed dispatch

The Store Methods

MethodPurpose
getState()The current state
dispatch(action)The dispatch
subscribe(listener)The subscribe
replaceReducer(next)The replace

The Types

TypePurpose
RootStateThe state tree type
AppDispatchThe dispatch type
TypedUseSelectorHookThe 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

PitfallWhy It HappensFix
The useSelector throwsNo ProviderWrap the app
The component re-rendersThe broad selectorSelect the slice
The state not updatedThe mutation outside the sliceUse the slice
The type errorsThe untyped hooksUse the typed hooks
The action not dispatchedThe wrong importImport from the slice
The middleware missingThe overrideUse getDefaultMiddleware
The DevTools disabledThe production buildThe 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

ItemValue
configureStoreCreate the store
ProviderMake the store available
useSelectorRead the state
useDispatchDispatch the action
createSliceCreate the reducer and the actions
extraReducersThe external handlers
RootStateThe state type
AppDispatchThe dispatch type
The typed hooksThe TypeScript
The ImmerThe immutable updates

Key takeaways:

  • The configureStore creates 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 Provider makes the store available to the tree. Every descendant reads the store with the useSelector and dispatches with the useDispatch. The Provider is the single wrap at the root.
  • The createSlice generates the reducer and the action creators. The name is the prefix, the initialState is the state, and the reducers is the map of the handlers. The .actions and the .reducer are 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 useAppSelector and the useAppDispatch are the typed from the RootState and the AppDispatch. The useSelector and the useDispatch are the untyped.
  • The extraReducers handles the external actions. The async thunks and the other slices’ actions are the cases. The builder.addCase is 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) => state re-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!