Prashanth Chandra
Prashanth Chandra

Reputation: 2692

React setState with multiple AJAX calls

I want the view to always be updated with the result of the latest call to searchWiki(). I've used a module that resolves repeated calls to $.ajax to the value returned by the most recent call.

It still seems to go out of sync and show the result of previous calls however. I'm guessing this is because setState is async? How should I keep the two async operations in sync?

In addition, I realize I should put in a debounce somewhere, but I'm not sure what I should debounce. handleChange, searchWiki or latestAjax?

Here is a demo: http://codepen.io/prashcr/pen/obXvWv
Try typing stuff then backspacing to see what I mean.

Search component

<div style={style}>
  <input 
    value={this.props.search}
    // this calls searchWiki with e.target.value
    onInput={this.handleChange}
  />
</div>

searchWiki function of parent component

searchWiki (search) {
  console.log('searchWiki called with: ' + search);
  if (!search) {
    this.setState({results: []});
  }
  else {
    // "latest" taken from https://github.com/bjoerge/promise-latest
    let latestAjax = latest($.ajax);
    let options = {
      url: this.props.url + search,
      dataType: 'jsonp'
    };
    latestAjax(options)
    .then(data => {
      var results = data.query.search.map(res => {
        res.url = 'http://en.wikipedia.org/wiki/' + encodeURIComponent(res.title);
        return res;
      });
      this.setState({results: results});
    })
    .error(e => console.log(e.message));
  }
}

Upvotes: 0

Views: 944

Answers (1)

Dan Prince
Dan Prince

Reputation: 30009

The handleChange function is too generic to be debounced with a hardcoded value as you might want to use this search component elsewhere. However, you still want to catch the repeating action as early as possible and ensure that it never does any unnecessary work.

Therefore I would suggest that you debounce the handleChange function with an optional prop, defaulting to 0ms.

getDefaultProps() {
  return {
    debounce: 0
  };
},
render() {
  // ...
  return (
    <div style={style}>
      <input
        // ...
        onInput={debounce(this.handleChange, this.props.debounce)}/>
    </div>
  );
}

Then make sure you pass this prop whenever you want to debounce the handler.

<Search onSearch={this.searchWiki} debounce={1000} />

Your other problem is happening because you are calling latest inside your searchWiki function and you only call the returned function once! Each time you call searchWiki you create a new latestAjax function.

For it to work, you'll need to call the returned function multiple times.

This means defining the wrapped $.ajax function outside of searchWiki function.

latestAjax: latest($.ajax),
searchWiki(search) {
  // ...
  this.latestAjax(options)
    .then(data => {

    });
}

Upvotes: 1

Related Questions