How do I show the loading indicator in a React Redux application while retrieving data?

I am new to React / Redux. I am using the fetch API middleware in a Redux application to process the API. This is ( redux-api-middleware ). I think this is a good way to handle asynchronous API actions. But I find some cases that I cannot solve on my own.

As stated on the home page ( Life Cycle ), the sampling API life cycle begins with the submission of the CALL_API action and ends with the submission of the FSA action.

So, my first case is showing / hiding the preloader when loading the API. The middleware will send the FSA action at the beginning and send the FSA action at the end. Both actions are taken by gearboxes, which should perform only normal data processing. No user interface operations, no more operations. Perhaps I should keep the processing state in a state, and then display them when updating the store.

But how to do that? Component full page response? What happens to updating the store from other actions? I mean, they are more like events than states!

Even in the worst case, what should I do if I need to use my own confirmation dialog or warning dialog in the Redux / Reaction applications? Where to put them, actions or gears?

Regards! I want to answer.

+96
javascript reactjs redux
Feb 17 '16 at 12:36
source share
8 answers

I mean, they are more like events than state!

I would not say that. I think loading indicators are a great example of a UI that is easily described as a state function: in this case, 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 the Redux repository, the 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 state of stores and returns isFetching as part of the return value of mapStateToProps() so it is available in the details of the connected components:

 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 display the label "Loading ..." (which may possibly be a spinner instead):

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

Even the worst case, what should I do when I need to use the built-in confirmation dialog or warning dialog in redux / react applications? Where should they be placed, actions or gears?

Any side effects (and, apparently, dialogue, certainly a side effect) do not apply to reducers. Think of gearboxes as passive "state builders." They really don't "do" things.

If you want to show a warning, either do it from the component before sending any action, or do it from the creator of the action. By the time the action is dispatched, it is too late to perform side effects in response to it.

There is an exception for each rule. Sometimes your logic of side effects is so complex that you really want to connect them to either specific types of actions or specific reducers. In this case, check out advanced projects like Redux Saga and Redux Loop . Just do it when you are comfortable with vanilla Redux and you have the real problem of diffuse side effects that you would like to make more manageable.

+147
Feb 26 '16 at 3:53 on
source share

Great answer Dan Abramov! I just want to add that I did everything more or less accurately in one of my applications (keeping isFetching as logical) and ultimately had to make it an integer (which ends with reading as the number of outstanding requests) to support multiple simultaneous requests.

with boolean:

request 1 start → counter on → request 2 start → request 1 ends → off counter → request 2 ends

with integer:

request 1 start → counter on → start request 2 → request 1 ends → request 2 ends → shutter speed

 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 
+19
Feb 26 '16 at 10:08
source share

I would like to add something. The real world example uses the isFetching field in the store to represent when collecting a collection of items. Any collection is generalized to a pagination reducer, which can be connected to your components to monitor the status and show whether the collection is loading.

It happened to me that I wanted to get details for a specific object that does not match the pagination template. I wanted to have a state representing if the details are retrieved from the server, but also I did not want to have a reducer just for that.

To solve this problem, I added another common reducer called fetching . It works similarly to the pagination reducer, and its duty is simply to observe many actions and generate a new state with [entity, isFetching] pairs. This allows you to connect reducer to any component and to know whether the application actually retrieves data not only for the collection, but also for a specific object.

+13
Feb 26 '16 at 8:06
source share

I still have not met this question, but since the answer is not accepted, I will throw my hat. I wrote a tool for this very job: react-loader-factory . This is a little more than Abramov’s solution, but more modular and convenient, since I didn’t want to think when I wrote it.

There are four large pieces:

  • Factory pattern: This allows you to quickly call the same function to set which states mean “Download” for your component and what actions need to be sent. (It is assumed that the component is responsible for triggering the expected actions.) const loaderWrapper = loaderFactory(actionsList, monitoredStates);
  • Wrapper: The component created by Factory is a “higher order component” (for example, that connect() returned in Redux), so you can just pin it to existing things. const LoadingChild = loaderWrapper(ChildComponent);
  • Action / Reducer interaction: the wrapper checks to see if the reducer it is connected to contains keywords that say they don’t pass the component that needs data. It is expected that actions sent by the wrapper will create related keywords (for example, how dispx-api-middleware sends ACTION_SUCCESS and ACTION_REQUEST ). (You can post actions elsewhere and, of course, follow the shell if you want).
  • Throbber: the component that you want to display until the data that your component depends on is ready. I added a little div so you can test it without having to fit it.

The module itself does not depend on redux-api-middleware, but this is what I use it with, so here is a sample code from README:

Component with bootloader:

 import React from 'react'; import { myAsyncAction } from '../actions'; import loaderFactory from 'react-loader-factory'; import ChildComponent from './ChildComponent'; const actionsList = [myAsyncAction()]; const monitoredStates = ['ASYNC_REQUEST']; const loaderWrapper = loaderFactory(actionsList, monitoredStates); const LoadingChild = loaderWrapper(ChildComponent); const containingComponent = props => { // Do whatever you need to do with your usual containing component const childProps = { someProps: 'props' }; return <LoadingChild { ...childProps } />; } 

A reducer for the bootloader to monitor (although you can lay it in different ways if you want):

 export function activeRequests(state = [], action) { const newState = state.slice(); // regex that tests for an API action string ending with _REQUEST const reqReg = new RegExp(/^[AZ]+\_REQUEST$/g); // regex that tests for a API action string ending with _SUCCESS const sucReg = new RegExp(/^[AZ]+\_SUCCESS$/g); // if a _REQUEST comes in, add it to the activeRequests list if (reqReg.test(action.type)) { newState.push(action.type); } // if a _SUCCESS comes in, delete its corresponding _REQUEST if (sucReg.test(action.type)) { const reqType = action.type.split('_')[0].concat('_REQUEST'); const deleteInd = state.indexOf(reqType); if (deleteInd !== -1) { newState.splice(deleteInd, 1); } } return newState; } 

I expect that in the near future I will add such moments as a timeout and an error, but the template will not differ much.




Short answer to your question:

  • Rendering rendering to rendering code - use a wrapper around the component you want to display, with data similar to the one I showed above.
  • Add a reducer that makes query status around an application that you might care about easily digestible, so you don’t need to think too much about what’s going on.
  • Events and status are not really different.
  • The rest of your intuitions seem true to me.
+5
Aug 29 '16 at 22:48
source share

You can add change listeners to your stores using either connect() from React Redux or the low-level store.subscribe() method. Your store should have a download pointer that the repository change handler can check and update the state of the component. Then the component adds a preloader depending on the state.

alert and confirm should not be a problem. They block and warn, do not even enter any data from the user. With confirm you can set the state based on what the user clicked if the user's choice affects the rendering of the component. If not, you can save the selection as a member variable for later use.

+3
Feb 17 '16 at 12:59
source share

We have three types of notifications in our application, all of which are designed as aspects:

  • Loading indicator (modal or non-modal based on the support)
  • Error popup (modal)
  • Snack information (non-modal, self-closing)

All three of them are at the top level of our application (Main) and are connected via Redux, as shown in the code snippet below. These details control the display of the relevant aspects.

I developed a proxy server that handles all our API calls, so all isFetching and (api) errors are mediated with actionCreators, which I import into the proxy. (As an aside, I also use webpack to introduce a support service layout for dev so we can work without server dependencies.)

Any other place in the application that should provide notifications of any type simply imports the corresponding action. Snackbar and Error have options for the displayed messages.

 @connect( // map state to props state => ({ isFetching :state.main.get('isFetching'), // ProgressIndicator notification :state.main.get('notification'), // Snackbar error :state.main.get('error') // ErrorPopup }), // mapDispatchToProps (dispatch) => { return { actions: bindActionCreators(actionCreators, dispatch) }} 

) export default class Main extends React.Component {

+3
Jul 08 '16 at 18:41
source share

Am I the only one who thinks that loading indicators are not part of the Redux store? I mean, I don’t think that this is part of the state of the application as such.

Now I am working with Angular2, and what I am doing is that I have a “Download” service that provides various loading indicators via RxJS BehaviourSubjects. I assume that the mechanism is the same, I just did not store the information in Redux.

LoadingService users simply subscribe to the events they want to listen to.

My Action Redux creators call the LoadingService function when something needs to be changed. UX components subscribe to open observables ...

+3
Sep 14 '16 at 12:59
source share

I save URLs like ::

 isFetching: { /api/posts/1: true, api/posts/3: false, api/search?q=322: true, } 

And then I have a remembered selector (by reselecting).

 const getIsFetching = createSelector( state => state.isFetching, items => items => Object.keys(items).filter(item => items[item] === true).length > 0 ? true : false ); 

To make url unique in case of POST, I pass some variable as a request.

And where I want to show the indicator, I just use the getFetchCount variable

+2
Feb 20 '17 at 15:58
source share



All Articles