Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to indicate "loading" state for an async action during first render using redux

Tags:

reactjs

redux

Say I have a redux module that looks like this:

import fetch from 'isomorphic-fetch';

// CONSTANTS
const LOAD = 'LOAD';
const LOAD_SUCCESS = 'LOAD_SUCCESS';

// REDUCER
const initialState = { loading: false, data: [] };

export default function reducer(state = initialState, action) {
  switch (action.type) {
    case LOAD: return { ...state, loading: true };
    case LOAD_SUCCESS: return { ...state, loading: false, data: action.data };
    default: return state;
  }
}

// ACTION CREATORS
function requestLocations() {
  return { type: LOAD };
}

function receiveLocations(json) {
  return { type: LOAD_SUCCESS, data: json };
}

export function fetchLocations() {
  return function (dispatch) {
    dispatch(requestLocations());

    return fetch('http://myurl')
      .then(response => response.json())
      .then(json => dispatch(receiveLocations(json)));
  };
}

I'm struggling with the loading state on the first render if I make the async call in componentWillMount. Imagine my component looks like this (simplified for brevity):

export default class LocationContainer extends React.Component {
  componentWillMount() {
    fetchLocations(); // already bound and injected via connect.
  }

  render() {
    const { loading, data } = this.props; // injected via connect from reducer above.
    if (loading) {
      return <Spinner />;
    } else {
      return <LocationExplorer locations={ data } />;
    }
  }
}

The problem I run into is that on the first render of LocationContainer, loading is false and data hasn't been fetched yet. In componentWillMount, the LOAD action is fired and a props change of loading being set to true is queued up to happen on the subsequent render. In the meantime, during my first render, LocationExplorer is rendered when I really wanted Spinner instead because loading is still false. I'm wondering how you deal with this without setting a firstRender = true state variable hack.

like image 446
nross83 Avatar asked Dec 12 '15 03:12

nross83


People also ask

How do you handle loading states in Redux?

The first thing we have to do is to create a separate reducer that will manage loading stuff. As we already know, we want a separate state for each request so, naturally, we can store these loading states in the array. This array will represent all active requests in our application.

How do you handle async actions in Redux?

Redux Async Data Flow​ Just like with a normal action, we first need to handle a user event in the application, such as a click on a button. Then, we call dispatch() , and pass in something, whether it be a plain action object, a function, or some other value that a middleware can look for.

What is useSelector in Redux?

UseSelector and useDispatch in React Redux useSelector and useDispatch are a set of hooks to use as alternatives to the existing connect() higher-order component. The equivalent of map state to props is useSelector. It takes in a function argument that returns the part of the state that you want.

Is Redux dispatch asynchronous?

However, the flow of Redux's state management tasks is completely synchronous: dispatching an action immediately generates the chain of calls to middleware and reducers to carry out the state transition.


1 Answers

One option could be extend your loading condition with your data initial state:

const initialState = { loading: false, data: [] };

When you are loading and your data is empty it means you are in this exact state of waiting for new data to come:

if (loading && data.length === 0) {
  return <Spinner />;
} else {

Also, I usually put my asynchronous calls in componentDidMount instead of componentWillMount.

like image 179
dreyescat Avatar answered Oct 14 '22 17:10

dreyescat