How to Implement Redux-Saga in React with TypeScript
Published: 2024-05-31 08:44:04

---
title: How to Implement Redux-Saga in React with TypeScript
published: true
description: Efficiently Handle State Management in React TypeScript applications with React-Redux, Redux-Saga, and Redux Toolkit.
tags:
# cover_image: https://dev-to-uploads.s3.amazonaws.com/uploads/articles/iny2qwvik6glz5zyf3be.png
# Use a ratio of 100:42 for best results.
# published_at: 2024-05-30 21:22 +0000
---

## How Redux-Saga with React-Redux works

Let’s see the following diagram to understand better:

![High-level architecture diagram of how redux-saga and react-redux works](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/iny2qwvik6glz5zyf3be.png)

Redux-Saga and React-Redux work together to manage asynchronous operations (like API calls) and side effects (like data manipulation) in React applications.

When a React component dispatches an action (e.g., “FETCH_DATA”) to the Redux store, it passes through Redux middleware, including Redux-Saga. Redux-Saga triggers the corresponding saga function if a saga is “watching” for that specific action type.

This saga function, written using generators, manages the asynchronous flow. It might make an API call using libraries like Axios or fetch. The saga can also perform other actions, like waiting for promises to resolve or dispatching new actions based on conditions.

Once the saga finishes its work (e.g., receiving data from the API), it might dispatch a new action to update the Redux store state. React components connected to the store using React-Redux will detect this state change and re-render themselves with the updated data, reflecting the results of the asynchronous operation in the UI.

### Let’s jump into the implementation.

First, let’s create a new React project by running the following command:

```sh
npm create vite@latest react-redux-saga-example -- --template react-ts
```

### Installing Packages

After creating a React project, let’s configure the project structure. Let’s first install React-Redux, Redux Toolkit, and Redux Saga. We can install these packages by running the following command:

```sh
npm install react-redux @reduxjs/toolkit redux-saga
```

Let’s also install other necessary packages which will be required in the future:

```sh
npm install axios react-router-dom
```

### Setup

After we install the necessary packages, let’s implement our project structure like the following:

![Project Structure](https://cdn-images-1.medium.com/max/2000/1*bmkkBgzcpUcpWjJoE8uVGQ.png)

Here, we can see some folders. Let’s discuss one by one:

 1. **Constants**: Contains some constant variables that can be used in multiple files and components.
 2. **lib**: Contains useful functions and libraries.
 3. **pages**: Contains different page components that will be rendered based on route URL.
 4. **store**: All the logic for `react-redux` and `redux-saga` goes into the store folder.
 5. **types**: Contains necessary types based on project domain and scope.

*[**Note:** This project can be found in this [SlackBlitz repo](https://stackblitz.com/edit/vitejs-vite-7cxqwy). So be sure to check that out.]*

### Understanding Redux Saga and React Redux

Now, let’s first create `store/index.ts` and paste the following code:

```ts
import createSagaMiddleware from '@redux-saga/core';
import { configureStore } from '@reduxjs/toolkit';
import rootReducers from './root-reducer';
import rootSaga from './root-saga';

const sagaMiddleware = createSagaMiddleware();

const store = configureStore({
  reducer: rootReducers,
  middleware: (getDefaultMiddleware) =>
    getDefaultMiddleware().concat(sagaMiddleware),
});

sagaMiddleware.run(rootSaga);

export default store;
```

This file configures the store to work with Reducer and integrate with redux-saga middleware. Don’t worry about the `rootReducers` and `rootSaga` for now, we will touch them eventually.

Let’s now create `root-reducer.ts` inside the same `store` folder, and paste the following code:

```ts
import usersReducer from './slices/users.slice';
import { UsersStateType } from '../types/user.types';

export type StateType = {
  users: UsersStateType;
};

const rootReducers = {
  users: usersReducer,
};

export default rootReducers;
```

In this code, we combine all the reducers to pass them into the store configuration.

Let’s now create the user reducer in the `store/slices/users.slice.ts` file and paste the following code:

```ts
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
import { USERS, UsersStateType, UserType } from '../../types/user.types';

const usersInitialState: UsersStateType = {
  user: {
    data: null,
    isLoading: false,
    errors: '' as unknown,
  },
  list: {
    data: [],
    isLoading: false,
    errors: '' as unknown,
  },
};

export const usersSlice = createSlice({
  name: USERS,
  initialState: usersInitialState,
  reducers: {
    getUserAction: (
      state: UsersStateType,
      { payload: _ }: PayloadAction<string>
    ) => {
      state.user.isLoading = true;
      state.user.errors = '';
    },
    getUserSuccessAction: (
      state: UsersStateType,
      { payload: user }: PayloadAction<UserType>
    ) => {
      state.user.isLoading = false;
      state.user.data = user;
    },
    getUserErrorAction: (
      state: UsersStateType,
      { payload: error }: PayloadAction<unknown>
    ) => {
      state.user.isLoading = false;
      state.user.errors = error;
    },
    getUserListAction: (state: UsersStateType) => {
      state.list.isLoading = true;
      state.list.errors = '';
    },
    getUserListSuccessAction: (
      state: UsersStateType,
      { payload: list }: PayloadAction<UserType[]>
    ) => {
      state.list.isLoading = false;
      state.list.data = list;
    },
    getUserListErrorAction: (
      state: UsersStateType,
      { payload: error }: PayloadAction<unknown>
    ) => {
      state.list.isLoading = false;
      state.list.errors = error;
    },
  },
});

export default usersSlice.reducer;
```

Here, we are implementing `usersSlice` using the `createSlice` method from the `redux-toolkit`. We are defining all the possible actions and returning the user reducer created from the `usersSlice`. Then, we pass the `userReducer` to the root reducer object.

Let’s now create our user saga to get the resources from the API. Let’s create `store/sagas/users.saga.ts` and paste the following code:

```ts
import { PayloadAction } from '@reduxjs/toolkit';
import { AxiosResponse } from 'axios';
import { put, takeLatest } from 'redux-saga/effects';
import { usersSlice } from '../slices/users.slice';
import apiClient from '../../lib/apiClient';
import { ApiEndpoints } from '../../constants/api';
import {
  GET_USER_BY_ID,
  GET_USER_LIST,
  UserType,
} from '../../types/user.types';

function* getUserSaga({ payload: id }: PayloadAction<string>) {
  try {
    const response: AxiosResponse<UserType> = yield apiClient.get(
      `${ApiEndpoints.USERS}/${id}`
    );
    yield put(usersSlice.actions.getUserSuccessAction(response.data));
  } catch (error) {
    yield put(usersSlice.actions.getUserErrorAction(error as string));
  }
}

function* getUserListSaga() {
  try {
    const response: AxiosResponse<UserType[]> = yield apiClient.get(
      `${ApiEndpoints.USERS}`
    );
    yield put(usersSlice.actions.getUserListSuccessAction(response.data));
  } catch (error) {
    yield put(usersSlice.actions.getUserListErrorAction(error as string));
  }
}

export function* watchGetUser() {
  yield takeLatest(GET_USER_BY_ID, getUserSaga);
  yield takeLatest(GET_USER_LIST, getUserListSaga);
}
```

Here, we fetch the API responses using the [generator function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function*) and yield the returned objects to the redux-saga middleware.

### Let’s see the API responses in action

First, let’s add the react-redux provider in our root component:

```ts
import React from 'react';
import ReactDOM from 'react-dom/client';
import { Provider } from 'react-redux';
import App from './App.tsx';
import store from './store/index.ts';

import './index.css';

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

Now, let’s see the below component how we can show the response in our component:

```ts
import { useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { StateType } from '../store/root-reducer';
import { usersSlice } from '../store/slices/users.slice';
import { Link } from 'react-router-dom';
import { PageRoutes } from '../constants/page-routes';

function Users() {
  const { list } = useSelector((state: StateType) => state.users);

  const dispatch = useDispatch();

  useEffect(() => {
    dispatch(usersSlice.actions.getUserListAction());
  }, []);

  return (
    <div>
      {list.isLoading ? (
        <span>Loading...</span>
      ) : list.data && list.data.length > 0 ? (
        <div>
          {list.data.map((user) => (
            <div key={user.id}>
              <Link to={`${PageRoutes.USERS.HOME}/${user.id}`}>
                {user.name}
              </Link>
            </div>
          ))}
        </div>
      ) : (
        <span>No users found!</span>
      )}
    </div>
  );
}

export default Users;
```

It will render the following display in our browser:

![Response from the API](https://cdn-images-1.medium.com/max/2000/1*cKoBVQTC2TChUABInwakxA.png)

That’s all in this article! I hope you enjoy it.

Have a great day!