Search code examples
reactjsaxioses6-promise

Cancel previous promise.all when onSearch is called again


I have a react component that has a autocomplete that calls onSearch when the user enters more than 3 characters.

Once this happens a function in a Util file (Requests.js) is called and returns an axios promise with data searched from those 3 characters.

Occasionally I need to call this function multiple times based on filtering needs so I loop however many times i need to and create an array of axios promises.

Then I promise.all it and display the results in the autocomplete.

My issue is if the user enters "tes" the process starts it could take a while to complete depending on their search term (this is fine). If they then type add a "t" (after the debounce window) and make the search term "test" another process is started but its synchronous and waits until the "tes" process completes before starting, finishing and displaying.

Obviously the first time onSearch is called it needs to start, but if any subsequent onSearches are called how can i cancel the previous Promise.all?


Solution

  • This is a prime use case for Observable Streams, but in the interest of keeping it simple, here's one idea.

    Whenever you do a search, increment a counter and save the value the counter is at when you start the search. Once the search is done, check if the counter changed; if it did, that search is no longer valid.

    (Untested) example in React (because your question is tagged as such):

    class MyComponent extends React.Component {
      state = { items: [] }
    
      searchCounter = 1
    
      constructor() {
        super(...arguments)
        this.debouncedSearch = debounce(this.search.bind(this), 500)
      }
    
      search(e) {
        const countBefore = ++this.searchCounter
        return axios.get('<url>', { params: { term: e.target.value} }).then(r => {
          // Another search happened before the response was done; cancel.
          if (countBefore !== this.searchCounter) {
            return
          }
          this.setState({ items: r.data })
        })
      }
    
      render() {
        return (
          <div>
            <input type="text" onKeyUp={this.debouncedSearch.bind(this)} />
            <ul>
              {this.state.items.map(item => (
                <li key={item.key}>{item.value}</li>
              ))}
            </ul>
          </div>
        )
      }
    }