How to build a type-safe React Redux app
Tags: react, redux, tutorial
Published: 2020-03-24 16:00:43

---
title: How to build a type-safe React Redux app
published: true
date: 2020-03-06 14:00:53 UTC
tags: react,redux,tutorial
canonical_url: https://blog.logrocket.com/how-to-build-a-type-safe-react-redux-app/
cover_image: https://dev-to-uploads.s3.amazonaws.com/i/k1ixhcbtl2yfrxjbvdu4.png
---
**Written by [Ganesh Mani](https://blog.logrocket.com/author/ganeshmani/)**✏️

Adding a type checking feature to your React application can help you catch lots of bugs at compile time. In this tutorial, we’ll demonstrate how to build a type-safe React Redux app by examining a real-world example.

To illustrate these concepts, we’ll create a sample e-commerce app like the one shown below.

![E-Commerce App E-Cart Example](https://i1.wp.com/blog.logrocket.com/wp-content/uploads/2020/03/ecart-example1-1.png?resize=700%2C599&ssl=1)

![E-Commerce App E-Cart Example](https://i1.wp.com/blog.logrocket.com/wp-content/uploads/2020/03/ecart-example2.png?resize=700%2C368&ssl=1)

![Completed E-Commerce App E-Cart Example](https://i1.wp.com/blog.logrocket.com/wp-content/uploads/2020/03/ecart-example-finished-1.gif?resize=700%2C438&ssl=1)

Without further ado, let’s get started!

[![LogRocket Free Trial Banner](https://i0.wp.com/blog.logrocket.com/wp-content/uploads/2017/03/f760c-1gpjapknnuyhu8esa3z0jga.png?resize=1200%2C280&ssl=1)](https://logrocket.com/signup/)

## Building a type-safe Redux app

React is a component library developers commonly use to build the frontend of modern applications. As an application expands and evolves, it often becomes increasingly difficult to manage the data. That’s where Redux comes in. Basically, Redux is a state management library that is popular in the React ecosystem. If you are new to the concept of [React Redux](https://redux.js.org/), I recommend reading the [official docs](https://redux.js.org/basics/example/) before proceeding with this tutorial.

Let’s start by building an e-commerce application workflow. Here, we have two important domains in the wireframe: inventory and cart.

![Diagram Showing Inventory and Cart Domains](https://i2.wp.com/blog.logrocket.com/wp-content/uploads/2020/03/inventory-cart-domains.png?resize=321%2C124&ssl=1)

First, we’ll create the essential Redux building blocks — namely, action creator, reducer, and store. Since we know the application domains, we’ll structure our app based on that.

Create a react application using this command:

```jsx
npx create-react-app react-redux-example --template typescript
```

This will create a React application boilerplate with TypeScript files. Next, install the dependencies for React Redux and its types.

```jsx
npm i react-redux redux redux-thunk
npm i --save-dev @types/react-redux
```

The above command should install the `redux` and `react-redux` libraries, which handle the React and Redux connection. Next, install `typesafe-action`, which helps to create an action with type check.

Now it’s time to create a file structure for our Redux store.

![Redux Store File Structure](https://i2.wp.com/blog.logrocket.com/wp-content/uploads/2020/03/redux-store-file-structure.png?resize=325%2C482&ssl=1)

The application store is structured based on the domain. You can see that all the actions, reducers, and sagas of the inventory domain are maintained in one folder, whereas the actions, reducers, and sagas of the cart domain are maintained in an another folder.

## Inventory domain

Let’s start with the inventory domain. We need to create actions, reducers, sagas, and types for the inventory domains. I always start with domain type because that way, I can define the structure of the specified domain at an early stage.

The type will contain the redux state, action types, and domain.

```jsx
 export interface Inventory {
  id: string;
  name: string;
  price: string;
  image: string;
  description: string;
  brand?: string;
  currentInventory: number;
}
export enum InventoryActionTypes {
  FETCH_REQUEST = "@@inventory/FETCH_REQUEST",
  FETCH_SUCCESS = "@@inventory/FETCH_SUCCESS",
  FETCH_ERROR = "@@inventory/FETCH_ERROR"
}
export interface InventoryState {
  readonly loading: boolean;
  readonly data: Inventory[];
  readonly errors?: string;
}
```

A few notes about the code above:

- The `Inventory` interface determines the specified domain data
- The `InventoryActionTypes` enum determines the action types
- The `Inventory` state handles the type of domain state

Now, it’s time to create an action for the inventory store.

```jsx
import { InventoryActionTypes } from "./types";
import { ActionCreator, Action, Dispatch } from "redux";
import { ThunkAction } from "redux-thunk";
import { ApplicationState } from "../index";
import inventory from "../../mockdata";

export type AppThunk = ActionCreator<
  ThunkAction<void, ApplicationState, null, Action<string>>
>;

export const fetchRequest: AppThunk = () => {
  return (dispatch: Dispatch): Action => {
    try {
      return dispatch({
        type: InventoryActionTypes.FETCH_SUCCESS,
        payload: inventory
      });
    } catch (e) {
      return dispatch({
        type: InventoryActionTypes.FETCH_ERROR
      });
    }
  };
};
```

First, we'll use Redux Thunk as a middleware in the action to make API calls. What is Redux Thunk, anyway? Basically, the action creator returns an object that has action type and payload. `redux-thunk` turns the action into a function that makes an API call in the intermediate and returns the data by dispatching an action.

Here, we have an action, `fetchRequest`, which basically returns a function. That function makes an API call (here, we mocked the inventory data instead of an API call). After that, it dispatches an action. 

We should also briefly mention type checking for action. Every action should be of type `ActionCreator`. Since we used Redux Thunk, each `ActionCreator` returns a function that has type `ThunkAction`.

If you're new to Redux Thunk, check out the excellent [documentation](https://github.com/reduxjs/redux-thunk) for an in-depth look.

The final part of the inventory store is the reducer. Let's create that file.

```jsx
import { Reducer } from "redux";
import { InventoryActionTypes, InventoryState } from "./types";
export const initialState: InventoryState = {
  data: [],
  errors: undefined,
  loading: false
};
const reducer: Reducer<InventoryState> = (state = initialState, action) => {
  switch (action.type) {
    case InventoryActionTypes.FETCH_REQUEST: {
      return { ...state, loading: true };
    }
    case InventoryActionTypes.FETCH_SUCCESS: {
      console.log("action payload", action.payload);
      return { ...state, loading: false, data: action.payload };
    }
    case InventoryActionTypes.FETCH_ERROR: {
      return { ...state, loading: false, errors: action.payload };
    }
    default: {
      return state;
    }
  }
};
export { reducer as InventoryReducer };
```

First, define an initial state that has a type of `InventoryState`.

```jsx
export const initialState: InventoryState = {
  data: [],
  errors: undefined,
  loading: false
};
```

After that, create a reducer with a state type of `InventoryState`. It's very important to define the types for each reducer because you want to identify issues at compile time rather than run time. 

```jsx
const reducer: Reducer<InventoryState> = (state = initialState, action) => {
  switch (action.type) {
    case InventoryActionTypes.FETCH_REQUEST: {
      return { ...state, loading: true };
    }
    case InventoryActionTypes.FETCH_SUCCESS: {
      console.log("action payload", action.payload);
      return { ...state, loading: false, data: action.payload };
    }
    case InventoryActionTypes.FETCH_ERROR: {
      return { ...state, loading: false, errors: action.payload };
    }
    default: {
      return state;
    }
  }
};
```

Here, we handle all the actions of the inventory domain and update the state. 

## Cart domain

It’s time to implement the redux functionalities for the cart. The functionalities of the cart domain are similar to those of the inventory domain.

First, create a file named `types.ts` and add the following code.

```jsx
import { Inventory } from "../inventory/types";
export interface Cart {
  id: number;
  items: Inventory[];
}
export enum CartActionTypes {
  ADD_TO_CART = "@@cart/ADD_TO_CART",
  REMOVE_FROM_CART = "@@cart/REMOVE_FROM_CART",
  FETCH_CART_REQUEST = "@@cart/FETCH_CART_REQUEST",
  FETCH_CART_SUCCESS = "@@cart/FETCH_CART_SUCCESS",
  FETCH_CART_ERROR = "@@cart/FETCH_CART_ERROR"
}
export interface cartState {
  readonly loading: boolean;
  readonly data: Cart;
  readonly errors?: string;
}
```

This represents the cart domain attributes, cart action types, and cart state of Redux.

Next, create `action.ts` for the cart domain.

```jsx
import { CartActionTypes, Cart, cartState } from "./types";
import { Inventory } from "../inventory/types";
import { ActionCreator, Action, Dispatch } from "redux";
import { ThunkAction } from "redux-thunk";
import { ApplicationState } from "../index";
export type AppThunk = ThunkAction<
  void,
  ApplicationState,
  null,
  Action<string>
>;
export const fetchCartRequest: AppThunk = () => {
  return (dispatch: Dispatch, state: ApplicationState): Action => {
    try {
      return dispatch({
        type: CartActionTypes.FETCH_CART_SUCCESS,
        payload: state.cart
      });
    } catch (e) {
      return dispatch({
        type: CartActionTypes.FETCH_CART_ERROR
      });
    }
  };
};
export const addToCart: ActionCreator<ThunkAction<
  void,
  ApplicationState,
  Inventory,
  Action<string>
>> = item => {
  return (dispatch: Dispatch): Action => {
    try {
      return dispatch({
        type: CartActionTypes.ADD_TO_CART,
        payload: item
      });
    } catch (e) {
      return dispatch({
        type: CartActionTypes.ADD_TO_CART_FAILURE,
        payload: null
      });
    }
  };
};
```

`action.ts` contains all the actions that handle the cart domain functionalities.

Here we're using `redux-thunk` to make an API fetch call. We mocked it for the purpose of this tutorial, but in production, you can fetch an API inside action creators.

Finally, write the code for the cart domain reducer. Create a file, name it `reducer.ts`, and add the following code.

```jsx
import { Reducer } from "redux";
import { CartActionTypes, cartState } from "./types";
export const initialState: cartState = {
  data: {
    id: 0,
    items: []
  },
  errors: undefined,
  loading: false
};
const reducer: Reducer<cartState> = (state = initialState, action) => {
  switch (action.type) {
    case CartActionTypes.FETCH_CART_REQUEST: {
      return { ...state, loading: true };
    }
    case CartActionTypes.FETCH_CART_SUCCESS: {
      return { ...state, loading: false, data: action.payload };
    }
    case CartActionTypes.FETCH_CART_ERROR: {
      return { ...state, loading: false, errors: action.payload };
    }
    case CartActionTypes.ADD_TO_CART: {
      return {
        errors: state.errors,
        loading: state.loading,
        data: {
          ...state.data,
          id: state.data.id,
          items: [...state.data.items, action.payload]
        }
      };
    }
    case CartActionTypes.REMOVE_FROM_CART: {
      return {
        errors: state.errors,
        loading: state.loading,
        data: {
          ...state.data,
          id: state.data.id,
          items: state.data.items.filter(item => item !== action.payload.id)
        }
      };
    }
    default: {
      return state;
    }
  }
};
export { reducer as cartReducer };
```

Now it's time to configure the store for our application.

## Configure store

Create a file named `configureStore.ts` in the root directory and add the following code.

```jsx
import { Store, createStore, applyMiddleware } from "redux";
import thunk from "redux-thunk";
import { routerMiddleware } from "connected-react-router";
import { History } from "history";
import { ApplicationState, createRootReducer } from "./store";
export default function configureStore(
  history: History,
  initialState: ApplicationState
): Store<ApplicationState> {
  const store = createStore(
    createRootReducer(history),
    initialState,
    applyMiddleware(routerMiddleware(history), thunk)
  );
  return store;
}
```

We created a function called `configureStore`, which takes `history`, and `initialState` as an argument.

We need to define the type for arguments such as `history` and `initialState`. `initialState` should have the type of `ApplicationStore`, which is defined in the store. The `configureStore` function returns the type `Store`, which contains the `ApplicationState`.

After that, create a store that takes the root reducer, `initialStore`, and middlewares. Next, run the saga middleware with the root saga.

We’re finally done with the Redux part. Next we’ll demonstrate how to implement the components for it.

## Components structure

Let’s zoom in on our components.

![Redux Store Component Structure](https://i0.wp.com/blog.logrocket.com/wp-content/uploads/2020/03/component-structure.png?resize=317%2C357&ssl=1)

- `HomePage` handles the main page, which renders the `ProductItem` component
- `Navbar` renders the navbar and cart items count
- `Cart` contains the list items that are added to the cart

Once you know how to structure a type-safe redux application, implementing components is fairly straightforward. Take the component part as an exercise and leave a comment below with your GitHub link.

You can find the complete source code for reference on [GitHub](https://github.com/ganeshmani/react-redux-typescript-example).

## What's next?

Now that you know how to build a type-safe Redux application using React Redux, Redux, and Redux Thunk, you may notice that it takes a lot of code just to set up Redux in your application.

Fortunately, there's a solution to this problem: [Redux Toolkit](https://redux-toolkit.js.org/introduction/quick-start). This package is designed to ease the process of implementing Redux.

Here is a quick outline of Redux toolkit:

- [`configureStore()`](https://redux-toolkit.js.org/api/configurestore/) is like a wrapper of `creatorStore()` in Redux. It comes with some Redux dev tools out of the box, eliminating the need to set it up
- [`createReducer()`](https://redux-toolkit.js.org/api/createreducer/) is a utility function that replaces the traditional reducers boilerplate in the Redux applications
- [`createAction()`](https://redux-toolkit.js.org/api/createaction/) is basically a helper function for defining Redux action types and creators 
- [`createSlice()`](https://redux-toolkit.js.org/api/createslice/) is a function that automatically generates action creators and action types based on an `initialState` and reducer function

## Summary

Adding a type check can help you avoid issues at compile time itself. For further reading, an understanding of the following concepts will help you along your type checking journey.

- Implementing [type checking for Redux Actions](https://github.com/piotrwitek/react-redux-typescript-guide#action-creators-)
- Adding type check for [action types, domain values, and domain state](https://redux.js.org/recipes/usage-with-typescript/#type-checking-state)
- Type checking for [reducers in the application](https://github.com/piotrwitek/react-redux-typescript-guide#reducers)
- Implementing type check for the [Redux store configurations](https://github.com/piotrwitek/react-redux-typescript-guide#store-configuration)
- Type check for [React Redux component connect](https://github.com/piotrwitek/react-redux-typescript-guide#--nested-hoc---wrapping-a-component-injecting-props-and-connecting-to-redux-)
- Adding [types for React components](https://github.com/piotrwitek/react-redux-typescript-guide#react---typing-patterns)

* * * 
## Full visibility into production React apps 

Debugging React applications can be difficult, especially when users experience issues that are difficult to reproduce. If you’re interested in monitoring and tracking Redux state, automatically surfacing JavaScript errors, and tracking slow network requests and component load time, [try LogRocket.](https://www2.logrocket.com/react-performance-monitoring)

![Alt Text](https://thepracticaldev.s3.amazonaws.com/i/eq752g8qhbffxt3hp9t4.png)

[LogRocket](https://www2.logrocket.com/react-performance-monitoring) is like a DVR for web apps, recording literally everything that happens on your React app. Instead of guessing why problems happen, you can aggregate and report on what state your application was in when an issue occurred. LogRocket also monitors your app's performance, reporting with metrics like client CPU load, client memory usage, and more.

The LogRocket Redux middleware package adds an extra layer of visibility into your user sessions. LogRocket logs all actions and state from your Redux stores.

Modernize how you debug your React apps — [start monitoring for free.](https://www2.logrocket.com/react-performance-monitoring)
* * *

The post [How to build a type-safe React Redux app](https://blog.logrocket.com/how-to-build-a-type-safe-react-redux-app/) appeared first on [LogRocket Blog](https://blog.logrocket.com).