1

I want render components only after successful asynchronous call. Before that, I want to show a Loading component to improve user experience.

In one component, I know how to handle with it, referring to Reactjs async rendering of components. But in my project, there are many pages(components) displaying infos with asynchronous call.

So is there a way to set a global Loading component in some place rather than write internal state in every component?

jason
  • 195
  • 1
  • 2
  • 12

4 Answers4

2

On before Every API Start

DISPATCH an action for API_START.

dispatch(apiStart());

const apiStart = ()=>{
    type : "API_START"
}

After finishing the API call, DISPATCH an action for API_FINSIH.

dispatch(apiFinish());

const apiFinish = ()=>{
    type : "API_FINSIH"
}

reducers :

const uiReducer = (state, action) => {

    switch (action.type) {
        case "API_START":
        return Object.assign({}, state, {
            requestsCOunt: state.requestsCOunt + 1
            });

    case "API_FINSIH":
        return Object.assign({}, state, {
            requestsCOunt: state.requestsCOunt - 1
        });
    }
}

In the component check for the state properties state.requestsCOunt ,

if it is state.requestsCOunt <=0,hide spinner

RIYAJ KHAN
  • 15,032
  • 5
  • 31
  • 53
  • if I use asynchronous thunks (as in `redux-thunk`) could this method lead to race conditions? – Yos Jan 16 '19 at 19:21
  • @Yos yes,But if there are multiple time API called then only and in this case, last one will update the state whatever. – RIYAJ KHAN Jan 17 '19 at 11:33
1

Maybe take a look to the HOC pattern in react: https://facebook.github.io/react/docs/higher-order-components.html

You could do something like that:

function withAsynchronous(WrappedComponent, url) {
  return class extends React.Component {
    constructor(props) {
      super(props);
      this.state = {
        isLoading: true
      };
    }

    componentDidMount() {
      // You async call
      GetAsync(url, data => {
        this.setState({ isLoading: false, data: data });
      });
    }

    render() {
      if (this.state.isLoading) {
        return (<YourLoadingComponent />);
      }

      return <WrappedComponent data={this.state.data} {...this.props} />;
    }
  };
}

And then render it like that:

componentDidMount() {
  const YourAsyncPage = withAsynchronous(
    YourPage,
    '/your/page/url'
  );
}

render() {
  return (<YourAsyncPage />);
}
Paul DS
  • 859
  • 7
  • 13
  • Hello @Paul DS, I read the HOCs,and learned a lot, many thanks. I did it like this: `const MyFund = Loading(WrappedComponent, 'wt/fund/0001.json'); export default MyFund;`and it works(Loading is a HOC). But I get a little confused about your last part, "And then render it like that: ..." How to use the code of this part? In the `componentDidMount` method and also in the `render`? – jason Aug 21 '17 at 11:31
  • I used the `componentDidMount` function only to show how to create the `YourAsyncPage` object, but you can do it where you want (in a specific file as you did is good). You just need to be careful to not create the object in the `render` method, to avoid recreating it at each rendering – Paul DS Aug 21 '17 at 17:43
1

You need a way to delegate the right portion of the sate to the component, so if your application has a login component then the handler in the login component should be passed the portion of the state that it is responsible for.

If loading looks different for components than you could have a loading member in your component state and set that to true or false using a function from a global utilities module or inject it from your application.

Handler of the application (action type is an array):

let utils = {
  toggleLoading:(sate)=>
    Object.assign(
      {}
      ,state
      ,{loading:!state.loading}
    )
}
switch(action.type.slice[0,1]) {
  case "login":
      return Object.assign(
        {}
        ,applicationState
        ,{
          login:
            loginHandler(
              applicationState.login
              ,Object.assign(
                {}
                ,action
                ,{type:action.type.slice(1)}
              )
              ,utils
            )
          }
      )
    }

login handler

switch(action.type.slice[0,1]) {
  case "toggleLoading":
    return utils.toggleLoading(state)
}

Since a component does not know how it's nested you have to pass the action type array from the application to the component and from components to sub components (action type can be added to state).

If you want to use one component to show and hide loading then the code is similar except your login component will delegate toggleLoading action to the loading component the same way as application delegates it to login.

No need to pass a utilities object in that case since there is only one loading component so you won't have duplicate implementation of how loading is set.

HMR
  • 37,593
  • 24
  • 91
  • 160
0

The basic example of async rendering of components with loading component is below:

import React                from 'react';
import ReactDOM             from 'react-dom';        
import PropTypes            from 'prop-types';
import YourLoadingComponent from './YourLoadingComponent.react.js';

export default class YourComponent extends React.PureComponent {
    constructor(props){
        super(props);
        this.state = {
            data: null
        }       
    }

    componentDidMount(){
        const data = {
                optPost: 'userToStat01',
                message: 'We make a research of fetch'
            };
        const endpoint = 'http://example.com/api/phpGetPost.php';       
        const setState = this.setState.bind(this);
        fetch(endpoint, {
            method: 'POST',
            body: JSON.stringify(data)
        })
        .then((resp) => resp.json())
        .then(function(response) {
            setState({data: response.message});
        });
    }

    render(){
        return (<div>
            {this.state.data === null ? 
                <YourLoadingComponent />
            :
                <div>{this.state.data}</div>
            }
        </div>);
    }
}
Roman
  • 19,236
  • 15
  • 93
  • 97