How to use Redux-Saga in a React App – Simple Blog Example

It is easier to build web applications with the React library alone. But if the app is getting bigger, we really need a state-management tool. React library itself contains Context API that works perfectly to initialize states and functions globally on our app. Also Redux, a third-party state-management tool gives more control over our state management. But we need middlewares like Redux-Thunk, Redux-Saga, etc. to handle the side-effects of Redux. Here we will discuss the steps to integrate or use Redux-Saga in a React app. We will build a simple blog as an example.

Prerequisites

To follow this article, the reader should be aware of the following technologies:-

  • JavaScript ES6
  • React.js library
  • Using Bootstrap in a React app
  • Basics for Redux state-management tool

What we will learn

In this article, we will learn the following things:-

  • What is Redux-Saga
  • To Create a React project
  • Adding Bootstrap(react-bootstrap) in our app
  • Creating multiple routes(act as pages)
  • Creating components in a React app
  • Integrating Redux-Saga in our app
  • Using third-party APIs
  • Structuring a React Reduxt-Saga project

What is Redux-Saga?

Redux-Saga is a middleware that handles the side effects of Redux. We might get confused with the word side effect.

What is a side effect?

The normal redux flow is when an action is dispatched, some state is changed. But when implementing the real-world use cases, we might need to call APIs, access local storage, etc., and change states according to the results. These processes are called side effects.

Redux can only change states with an action dispatch. But sometimes we need to call APIs, access local storage before changing the state. These are called side effects.

How to solve side effects?

We can solve these side effects by using middlewares like redux-thunk, redux-saga, etc. with pure redux.

Here in this article, we are discussing the Redux-Saga and not the Redux-Thunk.

How to use Redux-Saga in a React app

So let us start learning the steps of integrating Redux-Saga in a React app. Here we are going to build a simple blog in React and Redux-Saga. So that we can know the exact file structure and steps of building a large-scale app.

About the app we are going to build

By using a third-party API, we will get the list of posts and show this result on the home page as cards. Clicking each post will direct us to another page that displays the single post.

Also, clicking the title on the Navigation bar will direct us to the home page.

The below GIF will give us an idea about the workflow of the app.

I am also giving the complete file structure of the app for a better understanding of the app we are going to build.

So let us start coding the app from scratch.

Create a new React project

The first step is setting up a React application on your system. This can be easily done using the NPX tool.

So, install Node.js on your system first and create a react application using NPX. Don’t bother about the term NPX, because it’s a tool coming with NPM(Node Package Manager) 5.2+ onwards which will install on your system with Node.js itself.

If you need further assistance in the installation of React on your system, use the below links.

Install React on WindowsUbuntu, and macOS

npx create-react-app react-redux-saga-example-blog

This command will create a react application with the project name react-redux-saga-example-blog

Now enter the project directory and start the app.

cd react-redux-saga-example-blog
npm start

It will open up the React application we have created in our browser window with the address https://localhost:3000. The port may vary if 3000 is busy.

Now we can use our favorite code editor to edit our project. I personally recommend Visual Studio Code.

Integrate Boostrap in our app

We are using the react-boostrap package for integrating Bootstrap in our React app.

Install the packages React-bootstrap and Bootstrap in our app with the below command.

npm install react-bootstrap@next bootstrap@5.1.1

Now import the boostrap.min.css in the index.js file.

// index.js

import "bootstrap/dist/css/bootstrap.min.css";

Now can use React-bootstrap components inside our React app.

Add some custom SCSS styles

We are also adding custom-style files(SCSS) to our app. But to compile the SCSS styles, we need to install the node-sass package first. So, install the supported version 4.14.1 of node-sass in our app.

npm install node-sass@4.14.1

Now write the styles in /assets/scss/style.scss file.

We need to import this style file in the index.js file as well.

// index.js

import "./assets/scss/style.scss";

Implementing the multiple routes

We need two pages in our app. Home page to show the list of posts and another page to show the single post.

RouteCorresponding page
/Home page
/:idPost page

The /:id refers to a dynamic route where we need to replace the post id such as /1, /2, /3, etc.

This id will be used to fetch the corresponding single post from the API.

To implement multiple routes or pages in a react app, we need to install the react-router-dom package.

npm i react-router-dom

Now import the BrowserRouter component from the react-router-dom package in the index.js file.

// index.js

import { BrowserRouter } from "react-router-dom";

Also, wrap the entire app inside this BrowserRouter component.

// index.js

<BrowserRouter>
    <App />
</BrowserRouter>

We need to define the routes and the corresponding route components inside the App.js file.

// App.js

<Router>
  <Navigation />
   <Switch>
    <Route path="/" exact component={() => <Home />} />
    <Route path="/:id" exact component={() => <Singlepost />} />
   </Switch>
</Router>

We will code the Navigation component, Home component and SinglePost component later.

So that the complete App.js file is given below.

Setup a top Navigation

A top navigation bar with a title section is needed in our app where clicking the title will direct us to the home page.

Setup a .env file

I am setting up a .env file to store the API URL. Note that, every time you change the .env file, we should restart the app.

REACT_APP_APP_URL= "https://jsonplaceholder.typicode.com"

The Redux-Saga part

Almost all features are implemented in our app except the main part which is redux-saga. If you are already working with Redux projects, you will get a better understanding about the following steps.

Setup store and wrap the entire app inside Provider

First of all, set up a store and wrap the entire app inside a Provider component and pass the store as a prop. This is common for a Redux project.

Create a directory with name store and inside, create an index.js file.

We will discuss reducers and the sagas mentioned in this file later.

Now, import the created file and Provider component to our index.js file.

import store from "./store";
import { Provider } from "react-redux";

Now wrap the entire app with the Provider component and pass the store as a prop.

<Provider store={store}>
    <BrowserRouter>
      <App />
    </BrowserRouter>
</Provider>

So that the entire index.js file looks the same as below.

Define the action types and actions

Before defining the actions, we need to set up the action types. Action types are used to identify the corresponding reducer from the action. Let us define the action types first.

In this article, we are only dealing with the posts and so, create a directory named posts inside the store and add the actionTypes.js file.

Note that, in this file, we are just assigning the action type strings to variables for easily importing them in actions, reducers, and saga.

Now import these variables to an action file /store/posts/actions.js file.

import {
  GET_POSTS,
  GET_POSTS_SUCCESS,
  GET_POSTS_FAIL,
  GET_POST_DETAILS,
  GET_POST_DETAILS_SUCCESS,
  GET_POST_DETAILS_FAIL,
} from "./actionTypes";

Also, define all the actions related to posts inside it.

export const getPosts = () => {
  return {
    type: GET_POSTS,
  };
};

export const getPostsSuccess = (posts) => {
  return {
    type: GET_POSTS_SUCCESS,
    payload: posts,
  };
};

export const getPostsFail = (error) => {
  return {
    type: GET_POSTS_FAIL,
    payload: error,
  };
};
...

When we are loading the home page, it dispatches an action getPosts() just after the component is mounted.

It then reaches the corresponding action, reducer, saga and fetches the posts from API, and dispatches the getPostsSucces() action if the saga response is successful otherwise dispatches getPostsFail() if the saga response is a failure.

The complete actions.js file will be the same as mentioned below.

The Reducer part

Create a file reducer.js inside /store/posts directory. Import all the action types we declared earlier.

import {
  GET_POSTS,
  GET_POSTS_SUCCESS,
  GET_POSTS_FAIL,
  GET_POST_DETAILS,
  GET_POST_DETAILS_SUCCESS,
  GET_POST_DETAILS_FAIL,
} from "./actionTypes";

Now initialize the states needed for the posts.

const initialState = {
  posts: [],
  post: {},
  loadingPosts: false,
  loadingPostDetails: false,
  error: {
    message: "",
  },
};

Now, define the PostReducer.

const PostReducer = (state = initialState, action) => {
  switch (action.type) {

    case GET_POSTS:
      state = { ...state, loadingPosts: true };
      break;
    case GET_POSTS_SUCCESS:
      state = { ...state, posts: action.payload, loadingPosts: false };
      break;

    case GET_POSTS_FAIL:
      state = {
        ...state,
        error: {
          message: "Error",
        },
        loadingPosts: false,
      };
      break;

   ...
    default:
      state = { ...state };
      break;
  }
  return state;
};

The complete reducer file looks the same as below.

Coding the Saga portion

We need to import 3 APIs from redux-saga/effects which are takeLatest, put and call.

You might be confused with some terms here. I can explain them.

  1. call: Creates an Effect description that instructs the middleware to call the function fn with args as arguments.
  2. put: Creates an Effect description that instructs the middleware to schedule the dispatching of action to the store.
  3. Yield: The yield keyword pauses generator function execution and the value of the expression following the yield keyword is returned to the generator’s caller.
  4. takeLatest: Forks a saga on each action dispatched to the Store that matches pattern. And automatically cancels any previous saga task started previously if it’s still running.

Other than the takeLatest, there are APIs such as takeEvery, takeMaybe, etc.

For more, you can refer to the Saga docs for effect creators.

import { takeLatest, put, call } from "redux-saga/effects";
import { GET_POSTS, ... } from "./actionTypes";

import {
  getPostsSuccess,
  getPostsFail,
  ...
} from "./actions";

import { getPosts, getPostDetails } from "../../helpers/backend_helper";

function* onGetPosts() {
  try {
    const response = yield call(getPosts);
    yield put(getPostsSuccess(response));
  } catch (error) {
    yield put(getPostsFail(error.response));
  }
}
function* CartSaga() {
  yield takeLatest(GET_POSTS, onGetPosts);
  ...
}
export default CartSaga;

So that the complete saga.js file looks as below.

Make a common reducers file and saga file

We have created the reducer and saga for the posts. We have to import these to common reducers and sagas files.

So lets us code the reducers.js file first.

Now create sagas.js file as below.

Setting the helpers

From the previous files, we can see that some files are imported as helpers. Let us take a look at these files.

First, make a file url_helper.js inside the helpers directory. This is just for listing all the paths of our API.

Now let us make a file api_helper.js inside the same helpers directory. Here will will define all the functions to make API calls using the axios package.

Finally, we make a file backend_helper.js inside the directory and this is imported to our saga file directly.

If you are good at basic JS, you will understand the logic.

Coding the route components or pages

We have declared two routes / and /:id routes in the App.js file and now the / route returns the Home component and /:id returns the SinglePost component.

Now, let us define these two components. Even though they are components, we are placing them inside the pages directory because, in view, they are pages.

So inside the pages directory, create a Home component.

We will import useDispatch from the react-redux package and getPosts action from the actions file we created.

import { useDispatch } from "react-redux";
import { getPosts } from "../store/posts/actions";

Now we can dispatch the action getPosts just after the component is mounted.

let dispatch = useDispatch();

useEffect(() => {
    dispatch(getPosts());
}, []);

The complete Home component will be the same as below.

In the same manner, we will create a singlePost component. useParams from react-router-dom is used to get the URL parameter values.

import { useParams } from "react-router-dom";

Then we dispatch the function getPostDetails(params.id) if there is any change in params.id.

let params = useParams();

useEffect(() => {
    dispatch(getPostDetails(params.id));
}, [params.id]);

The component to show the single post will be the same as below.

Component to show the posts

To get the posts from the reducer, we are using the useSelector API by react-redux.

import { useSelector } from "react-redux";
import Loader from "react-loader-spinner";

export default function Posts() {

   const { posts, loadingPosts } = useSelector((state) => state.PostReducer);
...
}

Posts component will be the same as below.

Component to show the single post

In the same method, we access the state post from the reducer.

  const { post, loadingPostDetails } = useSelector(
    (state) => state.PostReducer
  );

The PostDetails component is given below.

Codesandbox

Refer to the CodeSandbox link to view the live app. You can clone this project to your CodeSandbox account and edit the code also.

https://codesandbox.io/s/react-redux-saga-example-blog-bnylq

GitHub

You can always refer to the GitHub repository to clone this project, refer to the code and work on top of it.

https://github.com/techomoro/react-redux-saga-example-blog

Summary

Here we discussed the steps to integrate or use Redux-Saga in a React app by creating a simple blog as an example. We used the exact file structure for building a large-scale react application. Because we commonly use Redux-Saga for a large-scale purpose.

Be the first to reply

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.