0

I'm going out of my mind with this one. I have a react class that has some methods like this:

handleSubmit = (e) => {
    this.setState({
        isLoading: true,
    }, this.doSearch());
}

changePage = (e) => {
    this.setState({
        searchPage: e,
        isLoading: true
    }, this.doSearch());
}

I use handleSubmit when the user clicks submit on the search form in this component's render.

<button type="button" id="btn-search" onClick={event => this.handleSubmit(event)}>Search</button>

This works out fine, it submits, sets the state and then calls this.doSearch after it sets isLoading: true.

Once I get the search results, I call a child component to display the result list, and I give it a handler for when the user clicks on pagination:

<ResultList searchResult={searchResult} changePage={this.changePage} />

From within the child, there is just a simple span with an onClick:

<span onClick={event => props.changePage(props.searchResult.page + 1)}>&gt;&gt;</span>

So when the user clicks on the next page ... pager, the handler is called. I can log out e and see the number has incremented. I then call setState and tell it to use the parent class's doSearch() after the state has been updated. Rather, that is my goal. But it keeps running doSearch() before the state has updated. But I know at some point the state finishes updating because by the time the component re-renders, the state has the updated searchPage.

The only way I seem to be able to get it to run my callback AFTER the state is done updating is to wrap it in an arrow function, so that it looks like so:

changePage = (e) => {
    this.setState({
        searchPage: e,
        isLoading: true
    }, () => {
        this.doSearch()
    });
}

This is only an issue with changePage which is passed to the child component and then called from there with the intention of incrementing or decrementing searchPage. Calling this.doSearch() from the submit handler is fine.

I get the feeling I'm not understanding scoping or this.state or instances or something....

lchow
  • 39
  • 5

2 Answers2

1

One calls it immediately and passes its return value as the callback to be used (bad). The other creates a function and that is used as the callback (good).

Its like accidentally writing an event listener like this:

// Assign console.log as the callbak
// Will print the event argument on click
// (just mentioning for completeness)
addEventlistener("click", console.log)

// vs

// Immediately print "a"
// Add undefined as callback
// (your first example [bad])
addEventlistener("click", console.log("a")):

// vs

// Add function that will print "a" after a click
// (your second example [good])
addEventlistener("click", () => console.log("a"))
zero298
  • 25,467
  • 10
  • 75
  • 100
0

What is confusing you is the order of evaluation in JavaScript, it's nothing React-specific.

If you have

foo(this.doSearch())

doSearch will be called immediately. That is, even before foo is called, and then the result of doSearch will be passed to foo.

You don't want that, you want to pass the computation to the setState function. That is, you want to pass the function doSearch, not the result of the function.

So what you actually want is

changePage = (e) => {
    this.setState({
        searchPage: e,
        isLoading: true
    }, this.doSearch);
}

Which means you're passing the function this.doSearch to setState. Of course, this.doSearch is equivalent to () => this.doSearch() (both are functions that, when called, call this.doSearch()).

YuriAlbuquerque
  • 2,178
  • 1
  • 11
  • 19
  • Ah... I shouldn't have `()`ed it. But for some reason `handleSubmit` seems to work, but maybe it's just fast enough that the loading message doesn't really seem impacted. That was hours of my life I won't get back. Much thanks. – lchow Sep 11 '20 at 02:29