Dave
Dave

Reputation: 19150

In React, is there a way I can submit my search request only when someone stops typing?

I'm using React 16.13.0. I have this search box where people type and expect to see results as they type ...

return (
  <div className="searchForm">
    <input
      type="text"
      placeholder="Search"
      value={this.state.searchTerm}
      onChange={this.handleChange}
    />

I use the below functions to handle retrieving results as people type ...

  handleChange(event) {
    const query = event.target.value;
    if (!query) {
      this.setState({ searchTerm: query, searchResults: [] });
    } else {
      this.setState({ searchTerm: query, loading: true }, () => {
        this.doSearch(query);
      });
    }
  }

  doSearch = (query) => {
    const searchUrl = "/coops/?contains=" + encodeURIComponent(query);
    fetch(searchUrl, {
      method: "GET",
    })
      .then((response) => response.json())
      .then((data) => {
        if (query === this.state.searchTerm) {
          this.setState({
            searchResults: data,
            loading: false,
          });
        }
      });
  };

I'm trying to cut down on the amount of network calls being made/processed. If someone types a string like "res" in a fairly rapid fashion, they probably don't care about the results for "r" or "re," so I was wondering, is there some way I can cancel a request if a new letter is entered in my search box? Or only execute the search if someone has stopped typing for at least 100ms (not sure if this is enough time, but decided to pick an arbitrary amount)?

Upvotes: 0

Views: 2140

Answers (3)

Christian Fritz
Christian Fritz

Reputation: 21364

What you are after is usually called debounce. There is a number libraries that implement this functionality, incl. lodash and underscore. In lodash, e.g., you would do the following:

      onChange={e => debouncedHandler(e, this.handleChange)}

and defined the following outside of the component:

const debouncedHandler = _.debounced((e, handler) => handler(e), 100);

It is a common pitfall to declare the debounced function inside the component itself. That will not work, because react reruns that code on every change and hence re-created the debounced function, hence the debouncing will not take place. Therefore it is important to define the debounced function outside the component.


Update: there was a mistake in the code I had earlier. It is fixed now, I think. I haven't tested this.

Upvotes: 2

Beso Kakulia
Beso Kakulia

Reputation: 602

You can use some helper like this and pass the calling function as the first argument callback.

function getDoneTyping (callback, wait = 300) {
  let timer
  const keyDownHandler = event => {
    if (event.key === 'Enter') {
      callback()
    }
    clearTimeout(timer)
  }

  const keyUpHandler = event => {
    clearTimeout(timer)
    timer = setTimeout(callback, wait)
  }
  return { keyDownHandler, keyUpHandler }
}

Upvotes: 0

95faf8e76605e973
95faf8e76605e973

Reputation: 14191

You can delay the searching via setTimeout. Just clear the timeout whenever a new input is caught. If timer expires, go and do the request

handleChange = (e) => {
    this.setState(
        {
            searchTerm: e.target.value
        },
        () => {
            try {
                clearInterval(window.xhrSearchTimer);
            } catch (e) {
                console.log(`no timeout initialized`);
            }

            window.xhrSearchTimer = window.setTimeout(() => {
                this.doSearch(this.state.searchTerm);
            }, 1000); // arbitrary value
        }
    );
};

doSearch = () => {
    alert(`Now searching for ${this.state.searchTerm}`);
};

https://codesandbox.io/s/loving-sid-hb0nr?file=/src/App.js:139-622

Upvotes: 0

Related Questions