Set Up a Typescript React Redux Project
Tags: react, redux, typescript, webdev
Published: 2020-03-15 18:31:22

---
title: Set Up a Typescript React Redux Project
published: true
description: This post provides a way to type your React Redux project with Typescript.
tags: react, redux, typescript, webdev
cover_image: https://dev-to-uploads.s3.amazonaws.com/i/dwq2n0v49yw0swb0snfd.png
---

![types](https://dev-to-uploads.s3.amazonaws.com/i/dwq2n0v49yw0swb0snfd.png)

# Introduction

This post provides a way to type your React Redux project with Typescript.

# Using the Ducks Pattern

This post loosely uses the [Redux Ducks](https://github.com/erikras/ducks-modular-redux) proposal, which groups Redux "modules" together rather than by functionality in Redux. For example, all of the Redux code related to the `users` piece of state lives in the same file rather than being scattered across different `types`, `actions`, and `reducer` folders throughout your app. If this isn't quite clear yet, you'll see what I mean shortly!

# Example App

As an example, let's pretend we're making a shopping cart app where we have a `user` that may or may not be logged in and we have `products`. These will serve as the two main parts of Redux state.

Since we're focused on Redux typings, let's bootstrap our app using `create-react-app` so we can get up and running quickly. Remember to give it the `--typescript` flag when you create the project.

```bash
yarn create react-app shopping-cart --typescript
```

Great! Now, let's go into our app directory and instal Redux and its types.

```bash
yarn add redux react-redux @types/redux @types/react-redux
```

# Setting Up our First Module

Let's create the `user` module. We'll do this by creating a `src/redux/modules/user.ts` file. We can define our `UserState` type and a couple action creators: `login` and `logout`.

Since we're not going to worry about validating passwords, we can just assume we only have a `username` prop on our `user` state that can either be a `string` for a logged-in user or `null` for a guest.

**src/redux/modules/user.ts**

```typescript
type UserState = {
  username: string | null;
};

const initialState: UserState = { username: null };

const login = (username: string) => ({
  type: 'user/LOGIN';
  payload: username;
});

const logout = () => ({
  type: 'user/LOGOUT'
});
```

Note that the `user/login` is a rough adaptation of the Redux Ducks proposal to name your types in the format `app-name/module/ACTION`.

Next, let's create a `user` reducer. A reducer takes the state and an action and produces a new state. We know we can type both our `state` argument _and_ the reducer return value as `UserState`, but how should we type the action we pass to the reducer? Our first approach will be taking the `ReturnType` of the `login` and `logout` action creators.

**src/redux/modules/user.ts**

```typescript
type UserState = {
  username: string | null;
};

const initialState: UserState = { username: null };

const login = (username: string) => ({
  type: 'user/LOGIN',
  payload: username,
});

const logout = () => ({
  type: 'user/LOGOUT',
});

type UserAction = ReturnType<typeof login | typeof logout>;

export function userReducer(
  state = initialState,
  action: UserAction
): UserState {
  switch (action.type) {
    case 'user/LOGIN':
      return { username: action.payload };
    case 'user/LOGOUT':
      return { username: null };
    default:
      return state;
  }
}
```

Unfortunately, we have a couple problems. First, we're getting the following Typescript compilation error: `Property 'payload' does not exist on type '{ type: string; }'`. This is because our attempted union type isn't quite working and the Typescript compiler thinks we may or may not have an action payload for the login case.

The second issue, which turns out to cause the first issue, is that the Typescript compiler doesn't detect an incorrect `case` in our `switch` statement. For example, if added a `case` for `"user/UPGRADE"`, we would want an error stating that it's not an available type.

How do we solve these issues?

## Function Overloads and Generics to the Rescue!

It turns out we can solve this issue by using Typescript _function overloads_ and _generics_. What we'll do is make a function that creates typed actions for us. The `type` created by this function will be a generic that extends `string`. The `payload` will be a generic that extends `any`.

**src/redux/modules/user.ts**

```typescript
export function typedAction<T extends string>(type: T): { type: T };
export function typedAction<T extends string, P extends any>(
  type: T,
  payload: P
): { type: T; payload: P };
export function typedAction(type: string, payload?: any) {
  return { type, payload };
}

type UserState = {
  username: string | null;
};

const initialState: UserState = { username: null };

export const login = (username: string) => {
  return typedAction('user/LOGIN', username);
};

export const logout = () => {
  return typedAction('user/LOGOUT');
};

type UserAction = ReturnType<typeof login | typeof logout>;

export function userReducer(
  state = initialState,
  action: UserAction
): UserState {
  switch (action.type) {
    case 'user/LOGIN':
      return { username: action.payload };
    case 'user/LOGOUT':
      return { username: null };
    default:
      return state;
  }
}
```

Success! We're now free of our compilation errors. Even better, we can be sure our cases are restricted to actual types we've created.

![types](https://dev-to-uploads.s3.amazonaws.com/i/dwq2n0v49yw0swb0snfd.png)

# Creating Our RootReducer and Store

Now that we have our first module put together, let's create our `rootReducer` in the `src/redux/index.ts` file.

**src/redux/index.ts**

```typescript
import { combineReducers } from 'redux';
import { userReducer } from './modules/user';

export const rootReducer = combineReducers({
  user: userReducer,
});

export type RootState = ReturnType<typeof rootReducer>;
```

If you're familiar with Redux, this should look pretty standard to you. The only slightly unique piece is that we're exporting a `RootState` using the `ReturnType` of our `rootReducer`.

Next, let's create our store in `index.tsx` and wrap our app in a `Provider`. Again, we should be familiar with this if we're familiar with Redux.

**src/index.tsx**

```typescript
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import { Provider } from 'react-redux';
import { createStore } from 'redux';
import { rootReducer } from './redux';

const store = createStore(rootReducer);

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

# Adding a Module with Thunks

Often, we'll need some async functionality in our action creators. For example, when we get a list of `products`, we'll probably be performing a fetch request that will resolve its Promise at some future time.

To allow for this asynchronous functionality, let's add `redux-thunk` and its types, which lets us return _thunks_ from our action creators.

```bash
yarn add redux-thunk @types/redux-thunk
```

Next, let's make sure to add this middleware when creating our `store`.

**src/index.tsx**

```typescript
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import { Provider } from 'react-redux';
import { createStore, applyMiddleware } from 'redux';
import { rootReducer } from './redux';
import thunk from 'redux-thunk';

const store = createStore(rootReducer, applyMiddleware(thunk));

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

Great! We can now create our `products` module, which will have the ability to return thunks from its action creators.

The product piece of our state will be a litte more complicated. It'll have a `products` prop, a `cart` prop, and a `loading` prop.

**src/redux/modules/products.ts**

```typescript
// TODO: We should move typedAction elsewhere since it's shared
import { typedAction } from './users';
import { Dispatch, AnyAction } from 'redux';

type Product = {
  id: number;
  name: string;
  price: number;
  img: string;
};

type CartItem = {
  id: number;
  quantity: number;
};

type ProductState = {
  products: Product[];
  loading: boolean;
  cart: CartItem[];
};

const initialState: ProductState = {
  products: [],
  loading: false,
  cart: [],
};

const addProducts = (products: Product[]) => {
  return typedAction('products/ADD_PRODUCTS', products);
};

export const addToCart = (product: Product, quantity: number) => {
  return typedAction('products/ADD_TO_CART', { product, quantity });
};

// Action creator returning a thunk!
export const loadProducts = () => {
  return (dispatch: Dispatch<AnyAction>) => {
    setTimeout(() => {
      // Pretend to load an item
      dispatch(
        addProducts([
          {
            id: 1,
            name: 'Cool Headphones',
            price: 4999,
            img: 'https://placeimg.com/640/480/tech/5',
          },
        ])
      );
    }, 500);
  };
};

type ProductAction = ReturnType<typeof addProducts | typeof addToCart>;

export function productsReducer(
  state = initialState,
  action: ProductAction
): ProductState {
  switch (action.type) {
    case 'products/ADD_PRODUCTS':
      return {
        ...state,
        products: [...state.products, ...action.payload],
      };
    case 'products/ADD_TO_CART':
      return {
        ...state,
        cart: [
          ...state.cart,
          {
            id: action.payload.product.id,
            quantity: action.payload.quantity,
          },
        ],
      };
    default:
      return state;
  }
}
```

There's a lot going on here, but the real novelty is in `loadProducts`, our action creator that returns a thunk. Our `setTimeout` function is simulating a fetch without having to actually perform a fetch.

We now need to register the `productsReducer` with our `rootReducer`. At this point, it's as easy as adding the respective key.

**src/redux/index.ts**

```typescript
import { combineReducers } from 'redux';
import { userReducer } from './modules/user';
import { productsReducer } from './modules/products';

export const rootReducer = combineReducers({
  user: userReducer,
  products: productsReducer,
});

export type RootState = ReturnType<typeof rootReducer>;
```

# Using In Our App

We're ready to use our Redux store! We've already added the `Provider` to our `index.tsx` file, so all we have to do is connect individual components.

Let's first connect an `Auth` component. We'll want to access the `user.username` prop from our state as well as the `login` and `logout` action creators.

**src/Auth.tsx**

```typescript
import React from 'react';
import { RootState } from './redux';
import { login, logout } from './redux/modules/user';
import { connect } from 'react-redux';

const mapStateToProps = (state: RootState) => ({
  username: state.user.username,
});

const mapDispatchToProps = { login, logout };

type Props = ReturnType<typeof mapStateToProps> & typeof mapDispatchToProps;

const UnconnectedAuth: React.FC<Props> = props => {
  // Do auth things here!
  return <>{props.username}</>;
};

export const Auth = connect(
  mapStateToProps,
  mapDispatchToProps
)(UnconnectedAuth);
```

Note that we define `mapStateToProps` and `mapDispatchToProps` at the to, which helps us derive the `Props` type using `ReturnType`. We now have access to `props.username`, `props.login`, and `props.logout` in our component.

## Dispatching Thunks

One wrinkle is when we want to map in an action creator that returns a thunk. We can use map in our `loadProducts` action creator as an example. In this case, we use Redux's handy `bindActionCreators` function!

**src/Products.tsx**

```typescript
import React from 'react';
import { RootState } from './redux';
import { loadProducts } from './redux/modules/products';
import { connect } from 'react-redux';
import { bindActionCreators, Dispatch } from 'redux';

const mapStateToProps = (state: RootState) => ({
  cart: state.products.cart,
});

const mapDispatchToProps = (dispatch: Dispatch) => {
  return bindActionCreators(
    {
      loadProducts,
    },
    dispatch
  );
};

type Props = ReturnType<typeof mapStateToProps> &
  ReturnType<typeof mapDispatchToProps>;

const UnconnectedProducts: React.FC<Props> = props => {
  // Do cart things here!
  return <>Your Cart</>;
};

export const Products = connect(
  mapStateToProps,
  mapDispatchToProps
)(UnconnectedProducts);
```

# Conclusion

And that's it! Not too bad to get the state management goodness of Redux with the type safety of Typescript. If you want to see a similar app in action, please check out the [associated github repo](https://github.com/nas5w/ts-redux).