Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Issuing AJAX request in the callback of setState()

I have been coding my ReactJS AJAX requests like this:

this.setState({
    isLoading: true,
    results: null
});
$.get(ajaxUrl, function(results) {
    this.setState({
        isLoading: false,
        results: results
    });
}.bind(this));

This is just an example, it lacks error handling, throttling, cancelling requests, etc. But the gist is there. I basically ask for some state to be set and then issue a request.

Looking at some other code on GitHub, though, I noticed that some people write their AJAX calls in the setState callback:

this.setState({
    isLoading: true,
    results: null
}, function() {
    $.get(ajaxUrl, function(results) {
        this.setState({
            isLoading: false,
            results: results
        });
    }.bind(this));
}.bind(this));

Is there a valid reason to do so? The docs say:

There is no guarantee of synchronous operation of calls to setState and calls may be batched for performance gains.

So there is no guarantee that setState mutates the state before returning, but I see no mention that different setState's may execute out of order. So the worst that could happen is that the "loading" state never gets rendered. Is this what the latter style is trying to solve? Is there some other risk I fail to see?

like image 293
Tobia Avatar asked Oct 20 '22 12:10

Tobia


1 Answers

but I see no mention that different setState's may execute out of order

Correct they are run in order, and from a UX perspective you don't want to show a loading indicator for less than half a second.

The case for needing this looks more like this:

this.setState({
    isLoading: true,
    results: this.state.results || []
}, function() {
    $.get(ajaxUrl, function(results) {
        this.setState({
            isLoading: false,
            // note! we're referring to state here
            results: this.state.results.concat(results)
        });
    }.bind(this));
}.bind(this));

However, this can also be solved by passing a callback to setState instead of the object. This way, we get to look at state after any previous queued updates have occurred.

It's also possible to pass a function with the signature function(state, props). This can be useful in some cases when you want to enqueue an atomic update that consults the previous value of state+props before setting any values.
– Component Api Docs

this.setState({
    isLoading: true,
    results: null
});
$.get(ajaxUrl, function(results) {
    this.setState(function(state){
        return {
            isLoading: false,
            results: state.results.concat(results)
        };
    });
}.bind(this));

For best results, abstract all of this into a high order component or mixin. You don't want to deal with this logic in every component that's fetching data.

like image 119
Brigand Avatar answered Oct 22 '22 23:10

Brigand