Jim Archer
Jim Archer

Reputation: 1417

Display spinner during AJAX call when using fetch API

In my project I am migrating to React and so not loading JQuery. Since I don't have JQuery anymore, for AJAX calls I am using fetch. With JQuery I can hook the start and end of AJAX calls so it's very easy to change the cursor to a spinner. I can't find similar hooks in fetch. Is there a way to do this other than changing it in each individual AJAX call?

Lots of Googling just kept finding answers about... JQuery.

Upvotes: 9

Views: 11808

Answers (4)

Braulio
Braulio

Reputation: 1728

The main issue I have found using the setting manually a "loading" flag to true / false is that is quite easy to skip by mistake the set to false flag and your application would like hanged), and on the other hand I dont' want to mess up when several ajax request are being called in parallel, this comes even more harder when you want to track some of them, but in other cases you want to make silent requests.

I have created a microlibrary called react-promise-tracker to handle this in a more automatic way. How it works

Install promise tracker

npm install react-promise-tracker --save

Implement a simple loading indicator component (you can add the style you need for it), and wrap it with the promiseTrackerHoc

import { promiseTrackerHoc } from 'react-promise-tracker';

const InnerLoadingIndicator = props => (
   props.trackedPromiseInProgress &&
   <h1>Hey some async call in progress ! Add your styling here</h1>
);

export const LoadingIndicator = promiseTrackerHoc(InnerLoadingIndicator);

Instantiate it in your application root component:

render(
  <div>
    <App />
    <LoadingIndicator/>
  </div>,
  document.getElementById("root")
);

Whenever you want to track a fetch or any promise based call (including async/await), just wrap it with the react-promise-tracker function trackPromise:

import { trackPromise } from 'react-promise-tracker';

export class MyComponent extends Component {
  componentWillMount() {
    trackPromise(
      userAPI.fetchUsers()
        .then((users) => {
          this.setState({
            users,
          })
        })
    );
  }

  // ...
}

More resources: - Step by step tutorial (including adding a nice looking spinner): https://www.basefactor.com/react-how-to-display-a-loading-indicator-on-fetch-calls

Upvotes: 2

Etienne Martin
Etienne Martin

Reputation: 11629

There you go, I think the code is pretty much self-explanatory:

// Store a copy of the fetch function
var _oldFetch = fetch; 

// Create our new version of the fetch function
window.fetch = function(){

    // Create hooks
    var fetchStart = new Event( 'fetchStart', { 'view': document, 'bubbles': true, 'cancelable': false } );
    var fetchEnd = new Event( 'fetchEnd', { 'view': document, 'bubbles': true, 'cancelable': false } );

    // Pass the supplied arguments to the real fetch function
    var fetchCall = _oldFetch.apply(this, arguments);

    // Trigger the fetchStart event
    document.dispatchEvent(fetchStart);

    fetchCall.then(function(){
        // Trigger the fetchEnd event
        document.dispatchEvent(fetchEnd);
    }).catch(function(){
        // Trigger the fetchEnd event
        document.dispatchEvent(fetchEnd);
    });

    return fetchCall;
};

document.addEventListener('fetchStart', function() {
    console.log("Show spinner");
});

document.addEventListener('fetchEnd', function() {
    console.log("Hide spinner");
});

Here's a live example: https://jsfiddle.net/4fxfcp7g/4/

Upvotes: 10

rie
rie

Reputation: 296

Fetch in thenable, you can add spinner stop function in then(), that is called after response is received. And wrap it in another function for headers and hooking.

function ajax(someUrl, method) {
    var myHeaders = new Headers();

    var myInit = { 
        method: method,
        headers: myHeaders
    };
    showSpinner();

    return fetch(someUrl, myInit).then(function(response) {
        //... do some with response
        hideSpinner();

        return response;
    }).catch(function(error) {
        //... tell the user the request failed and hide the spinner
        hideSpinner();

        return error;
    });
}

ajax('example.com').then(function(response) {
    // ...
}).catch(function(error) {
    // show error
});

Upvotes: 2

davidhu
davidhu

Reputation: 10472

What you can do when the fetch initially happens, you setState to loading as true. Then, once fetch is done, you can setState as loading is false. And you display the spinner based on the current state.

For example,

class SomeComponent extends React.Component {
  constructor(props) {
    super(props);
    this.state = { loading: false };
  }

  loadData(e) {
    e.preventDefault();
    this.setState({ loading: true });
    fetch(....).then( res => res.json()).then( resJson => {
      // do other stuff here.
      this.setState({ loading: false });    
    });
  }

  render() {
    return (
      <div>
         <DisplayComponent />
         <button onClick={this.loadData}>Click Me</button>
         { this.state.loading ? <Spinner /> : null }
      </div>
    );
  }
}

You can check out react conditional rendering

Upvotes: 0

Related Questions