Author avatar

Gaurav Singhal

How to Structure Redux Components and Containers

Gaurav Singhal

  • Jan 21, 2020
  • 11 Min read
  • 675 Views
  • Jan 21, 2020
  • 11 Min read
  • 675 Views
Languages Frameworks and Tools
Angular

Introduction

React is an excellent JavaScript-based library that allows us to create a single-page application. It has two significant features called state and props, which are used to communicate between the components.

But state and props are not enough to maintain the data throughout a React application because data cannot persist between the components.

To solve this, there is an architectural pattern that manages the application data so that it can be accessible to any component in the existing application. There are a few libraries that can be used to implement this architecture.

What is Redux?

The official definition of Redux from the Redux documentation is:

Redux is a predictable state container for the JavaScript apps.

This means Redux allows us to structure the app in such a manner that the data can be managed consistently and handled easily within the application.

To maintain the data globally in a React app, we can make use of Redux along with React by using a package called react-redux.

This package contains different add-ons so that we can use the various aspects of Redux, such as.

  • Creating and maintaining a global store
  • Creating reducers
  • Creating slices of the state
  • Managing sagas

By installing react-redux, we can configure our React app for Redux; hence we can construct our Redux app however we need to since there is no strict rule that defines how to structure a Redux app.

What is React-Redux?

react-redux is a library available using NPM, which is used to access different use cases of the Redux into our application.

Redux itself is a library that can be used along with the different platforms like Angular, React, Vue, and others as well to access the Redux add-on, but Redux is widely used with the React app.

One of the most significant advantages of using Redux with React is that it allows us to construct robust architecture by splitting the logic into smaller functions that manage its data.

So far, most front-end developers come to Redux because it has giant community support and they can get a response to questions in no time.

To install the redux, we can use the below npm command.

1
npm install redux
powershell

And to use Redux with React, we need to install the following package.

1
npm install react-redux
powershell

After installing these two packages, we will be able to get started with global data management using the Redux into our React application.

Structure the Redux Components and Containers

The essential requirement when it comes to the Redux app is how to structure it with the Redux configuration.

There is no single answer, but as per official guidelines, we have to create necessary folders and files to structure the Redux app.

Below is the sample folder structure for the simple demo app created using React and Redux.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
actiontypes/
    index.js
actions/
    EmployeeActions.js
    StudentActions.js
    AdminActions.js
components/
    Header.js
    Footer.js
    Logo.js
    EmployeeList.js
    StudentList.js
    AdminList.js
    EmployeeDetails.js
    StudentDetails.js
    AdminDetails.js
    FormValidationHelper.js
    ModalComponent.js
    Notification.js
    ButtonToolbar.js
containers/
    App.js
    Employee.js
    Student.js
    Admin.js
    Dashboard.js
reducers/
    index.js
    employee.js
    student.js
    admin.js
reStructuredText

As you can see in the above example, we have distributed everything in a folder . Let’s look closer at each folder.

Action Types

Action types are constants that contain the string value that identifies the type of action uniquely in the application.

For example, if we have a blog site, we have some operations to do, like fetch the blog list, fetch the single blog details, and so on. We can create the different action types as explained below.

1
2
3
export const FETCH_BLOGS = 'FETCH_BLOGS';
export const FETCH_BLOG_DETAILS = 'FETCH_BLOG_DETAILS';
export const CHANGE_PAGINATE = 'CHANGE_PAGINATE';
javascript

Each const contains the different string values that can be used along with the various files, such as with actions, reducers, and other files based on the functional requirements.

Actions

Actions are the simple functions used to initiate an HTTP call using any HTTP platform, such as fetch or Axios, and result in a response from the server.

Each action created in the application has an intention to fetch various information from the server using the API, which is created using back-end technologies such as Nodejs, Python, Java, spring-boot and so on.

Below is asample action function used to fetch the blog list using the API.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import { FETCH_BLOGS, FETCH_BLOG_DETAILS, CHANGE_PAGINATE } from '../actiontypes'
import axios from 'axios';

// Base URL fot the JSON API 
const ROOT_URL = 'http://localhost:3000/blogs';

// To fetch the blog list
export function fetchBlogs(paginate, category, sortby) {
    let request, url;
    url = `${ROOT_URL}?_page=${paginate.page}&_limit=${paginate.pageSize}`
    if ((typeof category !== "undefined")) {
        url += `&category=${category}`
    }
    if ((typeof sortby !== "undefined")) {
        url += `&_sort=${sortby}`
    }
    request = axios.get(url);

    return {
        type: FETCH_BLOGS,
        payload: request
    };
};
javascript

Here in this file, we have imported the action types that we have created previously. It also contains the HTTP call using the Axios package to get the list of blogs.

At the end of this function, we will return the type of function along with the payload used to manipulate the data using reducer. The return sections look like this.

1
2
3
4
return {
        type: FETCH_BLOGS,
        payload: request
    };
javascript

FETCH_BLOGS is an action type that we have defined into the separate file, and the payload is requested data used by the reducer.

Components

The component is the basic building block of any application. Thus the components created in this application are designed generically so that they can be re-usable throughout the application.

Each and every small functionality related to the single module can be divided into smaller chunks so that it can be accessible and readable pretty quickly.

For example, say we have to show a list of employees using employee data. As soon as a user clicks on a specific employee item, we have to show specific employee details. o do this, we can create two generic components, as stated below.

  • EmployeeList.js
  • EmployeeDetail.js

These two components are completely different from each other, and there is no relation between them. This way, we can divide the core functionality into smaller parts.

Containers

Containers are the primary components and the entry point from which we call child or generic components.

Basically, container components are called smart components because each container component is connected to Redux and consumes the global data coming from the store.

For example, we might have a container called BlogList connected to Redux, and the source code may look like this.

BlogList.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
import React, { Component } from "react";
import { connect } from 'react-redux';
import { Container, Row, Col, Button, Label, Input, FormGroup } from 'reactstrap';
import { fetchBlogs, fetchBlogDetails, changePaginate } from '../actions'
import moment from 'moment';

class BlogList extends Component {
    constructor() {
        super();
        this.state = {
            isMoreArticlesAvailable: true
        };
    }

    componentDidMount() {
        const { paginate, categoty } = this.props;
        localStorage.setItem('paginate', JSON.stringify(paginate))
        this.props.fetchBlogs(paginate, categoty, paginate && paginate.sortBy)
    }

    render() {
        const { blogs, title } = this.props;
        return (
            <Container>
                // List of blogs
            </Container>
        );
    }
}

const mapStateToProps = state => {
    return {
        blogs: state.blogs.all,
        paginate: state.blogs.paginate,
        totalCount: state.blogs.totalCount
    }
}

const mapDispatchToProps = dispatch => ({
    fetchBlogs: (paginate, category, sortingby) => {
        dispatch(fetchBlogs(paginate, category, sortingby));
    },
    fetchBlogDetails: (id, paginate) => {
        dispatch(fetchBlogDetails(id, paginate));
    },
    changePaginate: (paginate) => {
        dispatch(changePaginate(paginate));
    },
})

export default connect(mapStateToProps, mapDispatchToProps)(BlogList);
javascript

Let’s look at this container component.

componentDidMount() is used to call the action.

To initiate the action, we have to dispatch the action. Note the section called mapDispatchToProps, which is used to dispatch the action that receives the dispatch as method and returns the callback function.

1
2
3
4
5
6
7
8
9
10
11
const mapDispatchToProps = dispatch => ({
    fetchBlogs: (paginate, category, sortingby) => {
        dispatch(fetchBlogs(paginate, category, sortingby));
    },
    fetchBlogDetails: (id, paginate) => {
        dispatch(fetchBlogDetails(id, paginate));
    },
    changePaginate: (paginate) => {
        dispatch(changePaginate(paginate));
    },
}
javascript

We have dispatched three different actions along with the arguments so that the actual action method will get the payload, and further, it will be manipulated into the reducer.

Reducers

Reducers are normal functions used to manage a whole application’s state. They use the name of the action to be generated along with the action data.

Based on the action data, the state object can be manipulated using different switch cases.

One thing to keep in mind about the reducer is that it uses action.type and, based on the type the switch statement identifies, triggers an action. If there are no matching conditions, then the default section will be triggered.

Below is a simple example of a reducer.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import { FETCH_BLOGS, FETCH_BLOG_DETAILS, CHANGE_PAGINATE } from '../actiontypes';

const INITIAL_STATE = {
    all: [],
    blog: null,
    paginate: {
        page: 1,
        pageSize: 4,
        filter: '',
        sortBy: ''
    },
    totalCount: 12
};

export default function (state = INITIAL_STATE, action) {
    switch (action.type) {
        case FETCH_BLOG_DETAILS:
            return { ...state, blog: action.payload.data };
        case FETCH_BLOGS:
            return { ...state, all: action.payload.data }
        case CHANGE_PAGINATE:
            const paginate = { ...action.payload }
            return { ...state, paginate }
        default:
            return state;
    }
}
javascript

Based on the action type, different use cases will be compared, and if a matching action type is found, then the state object will be updated based on the functional requirement.

Note: This guide shows you some best practices for how to structure your React app with Redux. There is no specific structure you need to follow, and you can define your implementation according to your needs.

Conclusion

In this advanced guide about structuring a Redux app, we have learned what Redux is and how it can be used along with React by managing the global state with the help of container, component, reducer, and actions.

Thus we can distribute our Redux app on our own based on the application size as needed. I hope you learned a lot from this advanced React guide. Stay tuned for more upcoming guides.

3