Maciej Cieślar A JavaScript developer and a blogger @ https://www.mcieslar.com/

Methods for tracking action status in Redux

4 min read 1203

Methods For Tracking Action Status In Redux

Having worked on a fair share of React and Redux applications, I can’t help but notice that many people have a hard time indicating to the user that a given action is currently taking place.

Let’s consider the following example:

class RegisterForm extends React.Component {
 state = {
   isLoading: false
 }

 async handleSubmit(event) {
   event.preventDefault();

   this.setState({
     isLoading: true,
   });

   try {
     const result = await post('localhost:8000/api/users', {
       data: {},
     });
   } catch (error) {
     // do something with the error
   }

   // do something with the result
   this.setState({ isLoading: false });
 }

 render() {
   return (
     <form onSubmit={this.handleSubmit.bind(this)} >
       <input type="text" />
       <button type="submit">Submit</button>
       {this.state.isLoading && <p>Spinner!</p>}
     </form>
   );
 }
}

Here we have a simplified React register form that should display a loading indicator — say, a spinner — once the user has hit the submit button. Well, we could simply make the request inside the component and use setState to keep track of its status, and that would work just fine.

This solution has two problems, however. First, the request and its logic are defined inside a component; we would need to repeat this very same code should we want the same functionality elsewhere in our application.

Second, what if we wanted to display the spinner outside the component? How would we go about lifting that component’s state a few components up?

Here is where Redux comes to our aid.

By having an immutable global state available everywhere in our app, we can save the action’s status inside the state and have it available anywhere — thus, the indicator can be displayed anywhere. Let’s take a look at the usual asynchronous flow of actions in Redux.

The usual asynchronous action flow

Actions in Redux are objects and, as such, are dispatched synchronously. But thanks to various middleware, we can dispatch them in an asynchronous manner.

There are many libraries that allow us to dispatch actions asynchronously — redux-thunk, redux-saga, and redux-observable, to name a few.

The usual flow goes like this: first, we dispatch the action that is supposed to set things in motion (usually the action’s type ends with a _REQUEST suffix, e.g., GET_USER_REQUEST).

We made a custom demo for .
No really. Click here to check it out.

Then, somewhere in our state, we make a note that the action is pending, like this:

{
  isLoading: true
}

Or:

{
  pending: true
}

Note: I prefer the name pending because it doesn’t imply that the action is necessarily loading something.

Then, once the action is finished, we dispatch one of the following actions, depending on the outcome: GET_USER_SUCCESS or GET_USER_FAILURE.

Both of these actions will set the pending value to false and save (somewhere in the state) either the error or the result.

The simplest solution for storing the pending indicator

One common approach to handling the loading states of actions is to create a state of the following shape:

{
  user: {
    isLoading: true,
    user: {
      ...
    }
    token: '...'
  }
}

We can see here that we have a user section where we store all the user-related data.

This solution works well only for the most basic applications, and here’s why: What does isLoading tell us, exactly? There are many actions that may be considered user-related, such as registering, logging in, and updating; with this solution, we have no way of differentiating between them.

Each action on its own

A better approach to handling actions’ pending states is to create a separate object for each action we have.

Here’s an example:

{
  user: {
    register: {
      pending: false,
      error: null,
    },
    login: {
      pending: false,
      error: null,
    },
  }
}

This way, we can track a given action’s state throughout the whole application or identify specific actions as they occur. This allows us to display the register action’s state in multiple places in the application.

While a state like this is much more manageable, this solution still needs a lot of boilerplate code to be written for each action. Let’s consider a different approach, where we create a separate reducer for the pending indicators.

Creating a separate reducer

In Redux, each dispatched action executes all the reducers, regardless of whether a given reducer is even supposed to handle it.

By creating a separate reducer dedicated to keeping the pending states, we can use the SUCCESS and FAILURE actions to save the errors and results in other parts of the state.

Creating the reducer

Since the reducer will be executed on every action, we should filter out those we are not interested in: actions whose type doesn’t end with _REQUEST, _SUCCESS, or _FAILURE.

Since our convention is to name actions like GET_USERS_REQUEST, we can create a function called getActionName, in which we split the name at the _ character, remove the last part (REQUEST, SUCCESS, or FAILURE), and join the remaining parts with _.

function getActionName(actionType) {
 if (typeof actionType !== 'string') {
   return null;
 }

 return actionType
   .split("_")
   .slice(0, -1)
   .join("_");
}

If actionType is something other than a string, like a commonly used Symbol, we return null to avoid an error.

This way, we turn GET_USERS_REQUEST into GET_USERS and thus have a name under which we can save the pending state in the state.

Find Out How LogRocket Tracks
Redux Actions & State

Here’s the code for the reducer:

const pendingReducer = (state = {}, action) => {
 const { type } = action;
 const actionName = getActionName(type);

 if (!actionName) {
   return {
     ...state,
   }
 }

 if (type.endsWith("_REQUEST")) {
   return {
     ...state,
     [actionName]: {
       pending: true
     }
   };
 }

 if (type.endsWith("_SUCCESS") || type.endsWith("_FAILURE")) {
   return {
     ...state,
     [actionName]: {
       pending: false
     }
   };
 }

 return {
   ...state
 };
};

First, we check whether the action’s type ends with _REQUEST. If that is indeed the case, we create a new entry in the state with the action’s name as a key and { pending: true } as a value.

Then, if the action’s type ends with _SUCCESS or _FAILURE, we do the same thing, but this time we set { pending: false } as a value.

Now, should we want a user reducer, we can create it like so:

const userReducer = (state = initialUserState, action) => {
if (action.type === GET_USERS_SUCCESS) {
  return {
    ...state,
    user: action.payload.user,
  };
}
if (action.type === GET_USERS_FAILURE) {
  return {
    ...state,
    user: null,
  };
}
return { ...state };
};

Now we need not worry about setting pending: true on each action and then setting it back to false on success/failure.

Note: We don’t have error handling here, but it could also be done in a separate reducer.

Here’s a live demo for you to play with:

Summary

Assigning each action its own state to keep track of status is a scalable solution that relies on a lot of boilerplate code. By creating a separate reducer to handle the logic of managing status, we can reduce the amount of redundant code, but in turn, we lose the flexibility to define some additional fields needed to more accurately track a specific action’s status.

You come here a lot! We hope you enjoy the LogRocket blog. Could you fill out a survey about what you want us to write about?

    Which of these topics are you most interested in?
    ReactVueAngularNew frameworks
    Do you spend a lot of time reproducing errors in your apps?
    YesNo
    Which, if any, do you think would help you reproduce errors more effectively?
    A solution to see exactly what a user did to trigger an errorProactive monitoring which automatically surfaces issuesHaving a support team triage issues more efficiently
    Thanks! Interested to hear how LogRocket can improve your bug fixing processes? Leave your email:

    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.

    LogRocket 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 — .

    Maciej Cieślar A JavaScript developer and a blogger @ https://www.mcieslar.com/

    One Reply to “Methods for tracking action status in Redux”

    1. it does not harm the performance?
      Now all your components will subscribed to pendingReducer state, and react will run the diffing algorithm for each of the subscribed components.

    Leave a Reply