How to show a loading indicator in React Redux app while fetching the data?

I'm new to React/Redux. I use a fetch api middleware in Redux app to process the APIs. It's https://github.com/agraboso/redux-api-middleware. I think it's the good way to process async api actions. But I find some cases which can't be resolve by myself.

As the homepage https://github.com/agraboso/redux-api-middleware#lifecycle say, a fetch API lifecycle begins with dispatching a CALL_API action ends with dispatching a FSA action.

So my first case is showing/hiding a preloader when fetching APIs. The middleware will dispatch a FSA action at the beginning and dispatch a FSA action at the end. Both the actions are received by reducers which should be only doing some normal data processing. No UI operations, no more operations. Maybe I should save the processing status in state then render them when store updating.

But how to do this? A react component flow over the whole page? what happen with store updating from other actions? I mean they are more like events than state!

Even a worse case, what should I do when I have to use the native confirm dialog or alert dialog in redux/react apps? Where should they be put, actions or reducers?

Best wishes! Wish for replying.


I mean they are more like events than state!

I would not say so. I think loading indicators are a great case of UI that is easily described as a function of state: in this case, of a boolean variable. While this answer is correct, I would like to provide some code to go along with it.

In the async example in Redux repo, reducer updates a field called isFetching :

case REQUEST_POSTS:
  return Object.assign({}, state, {
    isFetching: true,
    didInvalidate: false
  })
case RECEIVE_POSTS:
  return Object.assign({}, state, {
    isFetching: false,
    didInvalidate: false,
    items: action.posts,
    lastUpdated: action.receivedAt

The component uses connect() from React Redux to subscribe to the store's state and returns isFetching as part of the mapStateToProps() return value so it is available in the connected component's props:

function mapStateToProps(state) {
  const { selectedReddit, postsByReddit } = state
  const {
    isFetching,
    lastUpdated,
    items: posts
  } = postsByReddit[selectedReddit] || {
    isFetching: true,
    items: []
  }

  return {
    selectedReddit,
    posts,
    isFetching,
    lastUpdated
  }
}

Finally, the component uses isFetching prop in the render() function to render a “Loading...” label (which could conceivably be a spinner instead):

{isEmpty
  ? (isFetching ? <h2>Loading...</h2> : <h2>Empty.</h2>)
  : <div style={{ opacity: isFetching ? 0.5 : 1 }}>
      <Posts posts={posts} />
    </div>
}

Even a worse case, what should I do when I have to use the native confirm dialog or alert dialog in redux/react apps? Where should they be put, actions or reducers?

Any side effects (and showing a dialog is most certainly a side effect) do not belong in reducers. Think of reducers as passive “builders of state”. They don't really “do” things.

If you wish to show an alert, either do this from a component before dispatching an action, or do this from an action creator. By the time an action is dispatched, it is too late to perform side effects in response to it.

For every rule, there is an exception. Sometimes your side effect logic is so complicated you actually want to couple them either to specific action types or to specific reducers. In this case check out advanced projects like Redux Saga and Redux Loop. Only do this when you are comfortable with vanilla Redux and have a real problem of scattered side effects you'd like to make more manageable.


Great answer Dan Abramov! Just want to add that I was doing more or less exactly that in one of my apps (keeping isFetching as a boolean) and ended up having to make it an integer (which ends up reading as the number of outstanding requests) to support multiple simultaneous requests.

with boolean:

request 1 starts -> spinner on -> request 2 starts -> request 1 ends -> spinner off -> request 2 ends

with integer:

request 1 starts -> spinner on -> request 2 starts -> request 1 ends -> request 2 ends -> spinner off

case REQUEST_POSTS:
  return Object.assign({}, state, {
    isFetching: state.isFetching + 1,
    didInvalidate: false
  })
case RECEIVE_POSTS:
  return Object.assign({}, state, {
    isFetching: state.isFetching - 1,
    didInvalidate: false,
    items: action.posts,
    lastUpdated: action.receivedAt

I'd like to add something. The real world example uses a field isFetching in the store to represent when a collection of items is being fetched. Any collection is generalized to a pagination reducer that can be connected to your components to track the state and show if a collection is loading.

It happened to me that I wanted to fetch details for an specific entity that doesn't fit in the pagination pattern. I wanted to have a state representing if the details are being fetched from the server but also I didn't want to have a reducer just for that.

To solve this I added another generic reducer called fetching . It works in a similar fashion to the pagination reducer and it's responsibility is just to watch a set of actions and generate new state with pairs [entity, isFetching] . That allows to connect the reducer to any component and to know if the app is currently fetching data not just for a collection but for an specific entity.

链接地址: http://www.djcxy.com/p/59052.html

上一篇: Redux中的异步操作

下一篇: 如何在读取数据时在React Redux应用程序中显示加载指示器?