ErnieKev
ErnieKev

Reputation: 3021

Add Loading Indicator for React-Redux app with Promise Middleware

I am new to react redux. I want to add a loading text when the user pressed the search button and dismiss the text when data comes back or the action completed. In a normal async function, I can just toggle the isLoading flag before and after the call back.

However, in my app, I am dispatching an action that returns a 'type' and 'payload' that is a promise. The middleware redux-promise then 'automatically' converts that promise payload and send it to the reducer. In other words, the middleware does the .then action for the promise behind the scene and gives my reducer the correct data.

In this case, I am not sure how I can add a loading text to my view because as soon as I call this.props.getIdByName(this.state.value), I do not know when the data comes back. The logical place for me would be inside the reducer since that is when the data comes back. However, I belive that would be a bad way because reducers should not perform such task?

Inside my component, I have the following function for my submit

  handleSubmit(e) {
    e.preventDefault();
    this.props.getIdByName(this.state.value);
  }

Inside my actions/index.js file, I have the following action generator

export function getIdByName(name) {
    const FIELD = '/characters'
    let encodedName = encodeURIComponent(name.trim());
    let searchUrl = ROOT_URL + FIELD + '?ts=' + TS + '&apikey=' + PUBLIC_KEY + '&hash=' + HASH + '&name=' + encodedName;
    const request = axios.get(searchUrl)

    return {
        type: GET_ID_BY_NAME,
        payload: request
    }
}

Inside my reducers/reducers.jsx

export default function (state = INITIAL_STATE, action) {
    switch (action.type) {
        case GET_ID_BY_NAME: 
            console.log(action.payload.data.data.results[0].id); <-- here the data comes back correctly because reduer called the promise and gave back the data for me
            return {...state, id: action.payload.data.data.results[0].id};
        default:
            return state;
    }
}

And in my main index.js file, I have the store created with the following middleware

const createStoreWithMiddleware = applyMiddleware(
    promise,
    thunk
)(createStore);

Upvotes: 3

Views: 2659

Answers (2)

Meticulous
Meticulous

Reputation: 125

I am sure Pedro's answer should get you started but I recently did the very exact loader/pre-loader in my app.

The simplest way to do it is.

  1. Create a new key in the state object in one of your reducers and call it showLoader: false.
  2. In the same container as before(the one with the button), create a mapStateToProps and get that state property showLoader.
  3. Inside the container that holds the button you are trying to trigger the loader with, add an onClick event which calls an action, say displayLoader. In the same function also set the this.props.showLoader to true
  4. Create a displayLoader action write something like this:

    function displayLoader() {
        return {
            type: DISPLAY_LOADER,
            payload: {}
        }
    }
    
  5. In the reducers, catch this action and set the showLoader back to false when your desired payload is received.

Hope it helps!

Upvotes: 1

Pedro Castilho
Pedro Castilho

Reputation: 10532

When you dispatch an action with a Promise as its payload while using redux-promise, the redux-promise middleware will catch the action, wait until the Promise resolves or rejects, and then redispatch the action, now with the result of the Promise as its payload. This means that you still get to handle your action, and also that as soon as you handle it you know it's done. So you're right in that the reducer is the right place to handle this.

However, you are also right in that reducers should not have side-effects. They should only build new state. The answer, then, is to make it possible to show and hide the loading indicator by changing your app's state in Redux :)

We can add a flag called isLoading to our Redux store:

const reducers = {
    isLoading(state = false, action) {
        switch (action.type) {
          case 'START_LOADING':
            return true;
          case 'GET_ID_BY_NAME':
            return false;
          default:
            return state;
        }
    },
    // ... add the reducers for the rest of your state here
}

export default combineReducers(reducers);

Whenever you are going to call getIdByName, dispatch an action with type 'START_LOADING' before calling it. When getIdByName gets redispatched by redux-promise, you'll know the loading is done and the isLoading flag will be set back to false.

Now we have a flag that indicates whether we are loading data in our Redux store. Using that flag, you can conditionally render your loading indicator. :)

Upvotes: 1

Related Questions