Search code examples
javascriptreactjsredux

Using react-redux and redux-thunk for loading indicator


I am working a project which use react-redux and redux-thunk for development. Previously the approach was to check api calls one by one and manually check if all the data is being fetched. As I found that after v7.0, batch is introduced in react-redux to help solving the issue. But the page also requires loading indicator as well.

The current approach is having several dispatches in batch to reduce unnecessary re-rendering, and manually check if all the data is fetched in the render, but I was wondering if there is any other method that can be applied on the batch to cut some hard code check.

Here is the current sample code:

// in action file
...
function fetchSomeData() {
    // call api to store data
    return dispatch => {
        batch(() => {
            dispatch(fetchData1());
            dispatch(fetchData2());
            dispatch(fetchData3());
            ..some more dispatches...
        });
    }
}
...

// in react component
dataLoaded(){
    ....retrieve all the data from different places... 
    if (!data1)    return false;
    if (!data2)    return false;
    ...check all the data...
    return true;
}

...
render() {
    if (this.dataLoaded()) {
        return actual_content;
    } else {
        return loading_content;
    }
}
...

I tried to directly use then, and create another method, return batch, call fetchSomeData, then use then(), but all produce "Cannot read property 'then' of undefined" error.

I also used Promise.all, but with no luck. Use of Promise.all is shown as below:

function fetchSomeData() {
    // call api to store data
    return dispatch => {
        Promise.all([
            dispatch(fetchData1());
            dispatch(fetchData2());
            dispatch(fetchData3());
            ..some more dispatches...
        ])
        .then(() => dispatch(setLoading(false)));
    }
}

I also checked other posts on the stackoverflow, but many posts suggest other middleware, and additional dependency requires approval as one of the requirement is limited bandwidth, use minimal dependencies as needed.


Solution

  • Redux Toolkit actually helped me to resolve this issue. A sample code looks like:

    • userSlice

      import { createAsyncThunk, createSlice } from '@reduxjs/toolkit';
      import axios from 'axios';
      
      export const userSlice = createSlice({
        name: 'user',
        initialState: {
          data: [],
          isLoading: false,
          error: null,
        },
        extraReducers(builder) {
          builder.addCase(fetchUsers.pending, (state, action) => {
            state.isLoading = true;
          });
          builder.addCase(fetchUsers.fulfilled, (state, action) => {
            state.isLoading = false;
            state.data = action.payload;
          });
          builder.addCase(fetchUsers.rejected, (state, action) => {
            state.isLoading = false;
            state.error = action.error;
          });
        },
      });
      
      export const fetchUsers = createAsyncThunk('users/fetch', async () => {
        const response = await axios.get(
          'https://jsonplaceholder.typicode.com/users'
        );
        console.log(new Date());
      
        return response.data;
      });
      
      export const usersReducer = userSlice.reducer;
      
    • postSlice

      // similar configuration as user
      function later(delay) {
        return new Promise(function (resolve) {
          setTimeout(resolve, delay);
        });
      }
      
      export const fetchPosts = createAsyncThunk('posts/fetch', async () => {
        await later(5000);
      
        const response = await axios.get(
          'https://jsonplaceholder.typicode.com/posts'
        );
      
        console.log(new Date());
      
        return response.data;
      });
      
    • third slice to call the thunks

      This action doesn't have to be async thunk, writing a custom thunk should also work.

      // similar configuration as previous
      export const fetchHome = createAsyncThunk(
        'home/fetch',
        async (_, thunkAPI) => {
          const res = await Promise.all([
            thunkAPI.dispatch(fetchUsers()),
            thunkAPI.dispatch(fetchPosts()),
          ]);
      
          console.log(res);
      
          return [];
        }
      );
      

    The result looks like:

    enter image description here

    The fetch home thunk waited until all the async thunks have been resolved and then emit the final result.

    reference: Dispatch action on the createAsyncThunk?


    I also found one utility created by solve this problem easier: Matching Utilities. It has provided 2 very helpful utility functions:

    • isAllOf - returns true when all conditions are met
    • isAnyOf - returns true when at least one of the conditions are met

    Using isAllOf would serve similar purpose as Promise.all/batch when dispatching multiple calls.