How to add Redux to a React app


This article goes over how to to add Redux to a React TypeScript app.

Table of Contents

Video

Watch YouTube video:

Demo

CodeSandbox demo:

Prerequisites

Given an app bootstrapped by Create React App:

npx create-react-app my-app --template typescript && cd my-app

Install

Install the dependencies:

With npm:

npm install @reduxjs/toolkit @types/react-redux react-redux

Or with Yarn:

yarn add @reduxjs/toolkit @types/react-redux react-redux

Slice

Create the slice:

// src/slice.ts
import { createSlice, PayloadAction } from '@reduxjs/toolkit';

const initialState = {
  value: 0,
};

export const name = 'counter';

const slice = createSlice({
  name,
  initialState,
  // The `reducers` field allows us define reducers and generate associated actions
  reducers: {
    increment: (state) => {
      // Redux Toolkit allows us to write "mutating" logic in reducers since
      // it doesn't actually mutate the state because it uses the Immer library,
      // which detects changes to a "draft state" and produces a brand new
      // immutable state based off those changes
      state.value++;
    },
    decrement: (state) => {
      state.value--;
    },
    // The `PayloadAction` type allows us to declare the contents of `action.payload`
    incrementByAmount: (state, action: PayloadAction<number>) => {
      state.value += action.payload;
    },
  },
});

export const { actions, reducer } = slice;

Store

Create the store:

// src/store.ts
import { configureStore } from '@reduxjs/toolkit';
import { name, reducer } from './slice';

const store = configureStore({
  reducer: {
    [name]: reducer,
  },
});

export default store;

Provider

Make the store available to all nested components with <Provider>:

// src/index.tsx
import { render } from 'react-dom';
import { Provider } from 'react-redux';
import Counter from './Counter';
import store from './store';

render(
  <Provider store={store}>
    <Counter />
  </Provider>,
  document.getElementById('root')
);

Component

Create the component:

// src/Counter.tsx
import { useDispatch, useSelector } from 'react-redux';
import { actions } from './slice';

export default function Counter() {
  const count = useSelector((state) => state.counter.value);
  const dispatch = useDispatch();
  return (
    <>
      <p>{count}</p>
      <button onClick={() => dispatch(actions.increment())}>+</button>
      <button onClick={() => dispatch(actions.decrement())}>-</button>
    </>
  );
}

The UI is integrated with Redux with hooks.

Not typing the callback function in useSelector will throw the TypeScript error:

Property 'counter' does not exist on type 'DefaultRootState'.

This can be fixed by typing state:

// src/Counter.tsx
// ...
import store from './store';
type RootState = ReturnType<typeof store.getState>;

export default function Counter() {
  const count = useSelector((state: RootState) => state.counter.value);
  // ...
}

Hooks

Alternatively, type the hooks:

// src/hooks.ts
import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux';
import store from './store';

type AppDispatch = typeof store.dispatch;
type RootState = ReturnType<typeof store.getState>;

// Use throughout your app instead of `useDispatch` and `useSelector`
export const useAppDispatch = () => useDispatch<AppDispatch>();
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;

For easier use in components:

 // src/Counter.tsx
 import { useDispatch, useSelector } from 'react-redux';
 import { actions } from './slice';
-import store from './store';
-type RootState = ReturnType<typeof store.getState>;
+import { useAppDispatch, useAppSelector } from './hooks';

 export default function Counter() {
-  const count = useSelector((state: RootState) => state.counter.value);
-  const dispatch = useDispatch();
+  const count = useAppSelector((state) => state.counter.value);
+  const dispatch = useAppDispatch();
   return (
     <>
       <p>{count}</p>
       <button onClick={() => dispatch(actions.increment())}>+</button>
       <button onClick={() => dispatch(actions.decrement())}>-</button>
     </>
   );
 }

Resources



Please support this site and join our Discord!