Introducing Redux

7 Min. Read
May 23, 2021

Understanding Redux

As react is all about rendering content on a screen and handling user interaction, its primary goal is not really around maintaining, updating or otherwise handling data. Which is why when a system starts growing bigger in size and we are required to work with several interconnected data among the components, the data handling becomes incredibly unmanaged ultimately resulting to an inefficient and difficult to manage system. So, state management libraries had to be introduced. There are different state management libraries that has been serving react namely: Redux, Recoil and Mobx. In this article we will get to understand Redux more simply.
As defined by the docs, Redux is a Predictable State Container that handles the state of a system irrespective of the environment it is run in. To simply put it, its a state management system. As of today, react-redux have become a popular pair in web development. However, Redux was not built and explicitly designed to work with react. We can also see ports of Redux being used in other different languages as well.

The Redux system can be overwhelming to most of us beginners at first but when we start getting the hang of it and understand its workflow, creating even complex applications would seem much simpler and managed.

The Analogy

Let us consider you are building an application for a cosmetic brand where customers can each have a marketing staff to help them choose products. In the current page you are working, you need to check all the list of customers, all your staffs and all the products. It would be a cumbersome job to call an API for each component and pass data as a prop. So what Redux does is it provides its own store which contains all the data (as customer, marketing staff and products in this analogy) and each and every component will have their access to the redux store data. This is one major feature that makes Redux so much useful.


Creating and configuring the store

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import { createStore, combineReducers } from 'Redux';
import customer from './customerReducer';
import staff from './staffReducer';
import products from './productsReducer'; 

export const ConfigureStore = () => {

    const appReducer = combineReducers({
        customer: customer,
        staff: staff,
        products: products,
    });

    const store = createStore(appReducer);

    return store;
};

Here, we have created a redux store using createStore function from Redux and assigned the store to a variable named store. If we take a look at the appReducer variable we can find the combineReducers function that takes an object as a parameter. This combineReducers function, as its name suggests, combines multiple reducers to make a single object.
Why do we need multiple reducers? There are different category of data that a system uses like in our case is customer data, the staffs data and products data. All these data represents different entities and serve their own purpose. Thus, it is a logical and a common way to separate them and manage data on store

Reducers

Reducers have a sole purpose: storing and updating states. Suppose the store is an inventory then the reducers are inventory workers. They store data, and update them if there is any action request from outside.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import * as ActionTypes from '../actions/ActionTypes';

const initialState = {
    staff: null,
    allStaff: [],
};

export default function staff (state = initialState, action) {
let index; 
    switch (action.type) {
        case ActionTypes.SET_ALL_STAFF:
            return { ...state, allStaff: action.payload.results };
        case ActionTypes.SET_STAFF:
            return { ...state, staff: action.payload.results };
        default:
            return state;
    }
}

This is the staffReducer file that we imported while creating store. This reducer contains two major sections. Their initial state and the actions to modify the state. The Initial state is the data available at the redux store at any given time.
Reducers change the data only when an action is invoked. The way this reducer changes the state is by checking the invoked action and updating the data based on the action. So if I want to add all the staff data to the store, I will check the ActionType of SET_ALL_STAFF and pass all the staff data I have on the action.payoad and update the state. Now we are having new terminologies here and some unresolved questions.
What is this action type? What is action.payload? Who sends the action.type which determines what actions to perform and which state to update? All this relates to the next important part of Redux which is actions.

Actions

Actions are the functions that dispatch those action.type and action.payload that reducers need in order to change the sates. It is the dispatch function that makes the action. Dispatch is the function of the Redux store and is the only way to trigger state change.
So here in the example we are loading the data of all the staffs. We do that by envoking the action type SET_ALL_STAFF. The way it is envoked is by the dispatch function. It provides action type and the data named here as payload. The data is provided if it is required by the action.

1
dispatch({ type: 'SET_ALL_STAFF', payload: all_staff_data})}

Note: The action type is just the way to make state changes more readable and understandable. It is only the string or name given to any action.
The dispatch
Usually, the dispatch functions are called within a function as these actions has to be dispatched in certain conditions and during some event.

1
2
3
4
5
6
7
8
9
export const getAllStaffData = () => async (dispatch) => {
    try {
        const response = await fetch(`url`, {method: 'GET'});
        dispatch({ type: ActionTypes.SET_ALL_STAFF, payload:response });
        return response;

    } catch (e) {}
};

The function above first makes the get request to an api that provides us the data of all the staffs in response. The response is then passed to the payload with action type to let the dispatch load all the data into the store. The reducer knows which state to change by referring the action type given by dispatch.

Feeding the components

This was all about creating store and changing its state. But how do we use the store value in the components? This is the part where we actually take the advantages of all the troubles we went through setting up Redux.
First thing first, we need to make the store available to our components. To say, we provide the store to our app using

1
2
3
4
5
6
7
import { Provider } from 'react-Redux'
ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>
)

We wrap our app (App.js or any parent components that would need the store) inside component. And we pass our store (that is declared during the configuration) as a prop. Now all our components inside the parent component will have the access to the store.

Feeding the components

We commonly use the connect function on the component itself to connect react components to the Redux store. Through connect, we can provide components only the required states and actions. Like so:

1
export default connect( mapStateToProps, { Action Functions })(ComponentName);

The connect function takes mapStateToProps as a first argument. mapStateToProps is a function that takes state as its first parameter and assigns only the required data to an object key which is then passed as a prop to the same component and can be used anywhere inside it.

1
2
3
4
5
const mapStateToProps = (state) => ({
    currentstaff: state.staff.staff,
    allStaffs: state.staff.allStaff,
});

As in our early example, we made a reducer called staffreducer which had the state variables called staff and allStaff. In doing the above, the values of those states will be assigned to the keys currentStaff and allStaffs respectively. These keys can now be added as a prop to make it accessible to the component.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import React, { useEffect } from 'react';
import { connect } from 'react-redux';
import getAllStaffData from './actionStaff'

const Staffs = ({currentstaff, allStaffs, getAllStaffData}) => {

    useEffect(() => {
        getAllStaffData()
    }, [])

    return (
        <div>
            {console.log(currentstaff, allStaffs)}
        </div>
    )
}
const mapStateToProps = (state) => ({
    currentstaff: state.staff.staff,
    allStaffs: state.staff.allStaff,
});

export default connect( mapStateToProps, { getAllStaffData })(Staffs);

When we need to call dispatch actions inside the component

As we mentioned earlier, the only way to change the Redux states is with dispatch functions and mostly some kind of action from a UI is required to initiate the process of changing Redux store. Now the question is how we can access the action function inside a component in order to call it when required.
There are only few steps when using the connect function.
We first import the action function where we have our dispatch -> add it as a second parameter inside connect as shown above -> pass it to the component props. Now we can use the functions inside the component.

Wrapping up

In this article, we have discussed how Redux store is created, how is it manipulated and how we can use the store and make changes from inside the component. These are the basic setup required to be working with react. However, there are other different tools that Redux provides when working with react: Redux hooks like useSelector, useDispatch and different other libraries that work with react to make the flow simpler.

Finally the summed up Redux-cycle

Extracted from: Modern React with Redux