Carl Rippon

Building SPAs

Carl Rippon
BlogBooks / CoursesAbout
This site uses cookies. Click here to find out more

Search After Stop Typing React Component

August 31, 2017
reacttypescript

In this post I’m going to go through how to implement a search criteria component that only invokes the search in a calling component after the user has stopped typing.

search criteria diagram

So, we have the following search criteria component that invokes the search on a submit button at the moment:`// SearchCriteria.tsx

// SearchCriteria.tsx

import * as React from "react";

interface SearchCriteriaProps {
  placeholder: string;
  doSearch: (criteria: string) => void;
}
interface SearchCriteriaState {
  criteria: string;
}

class SearchCriteria extends React.Component<
  SearchCriteriaProps,
  SearchCriteriaState
> {
  constructor() {
    super();

    this.state = {
      criteria: ""
    };
  }

  handleCriteriaChange = (e: React.FormEvent<HTMLInputElement>) => {
    this.setState({ criteria: e.currentTarget.value });
  };

  handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    this.props.doSearch(this.state.criteria);
  };

  render() {
    return (
      <form onSubmit={this.handleSubmit}>
        <div className="input-group">
          <input
            type="text"
            placeholder={this.props.placeholder}
            value={this.state.criteria}
            onChange={this.handleCriteriaChange}
            className="form-control"
          />
          <span className="input-group-btn">
            <input type="submit" value="Submit" className="btn btn-secondary" />
          </span>
        </div>
      </form>
    );
  }
}

export default SearchCriteria;

… and this component is referenced like the following in a calling component:

<SearchCriteria placeholder="Enter company name ..." doSearch={this.doSearch} />

At the moment, our submit button triggers the form’s submit which in tern calls handleSubmit() which then calls this.props.doSearch in our calling component to invoke the search (our calling component actually does the search).

search criteria submit diagram

Here’s what our component looks like at the moment:

initial search criteria component

We want to remove the submit button and just have the search invoke when the user stops typing.

So, let’s first remove the form and submit button from our render function:

render() {
  return (
    <div className="input-group">
      <input
        type="text"
        placeholder={this.props.placeholder}
        value={this.state.criteria}
        onChange={this.handleCriteriaChange}
        className="form-control"
      />
    </div>
  );
}

Now let’s just invoke the search in handleCriteriaChange after we have set the criteria in the state:

handleCriteriaChange = (e: React.FormEvent<HTMLInputElement>) => {
  this.setState({ criteria: e.currentTarget.value }, () => {
    this.props.doSearch(this.state.criteria);
    console.log(`doSearch(${this.state.criteria})`);
  });
};

So, our component invokes the search on every keystroke that the user does: search criteria component invoke every keystroke

Not quite what we want!

We need to “debounce” the search. That’s where the debounce function in the excellent lodash library comes in.

So, let’s bring lodash and the TypeScript types into our project:

npm install --save lodash
npm install --save @types/lodash

We now need to import just the debounce function into our component:

import { debounce } from "lodash";

If we look at the docs, debounce just creates another function that delays calling another function. So, let’s create our “debounce” function leaving the console.log() so that we can check that doSearch is only being called when the user has stopped typing:

raiseDoSearchWhenUserStoppedTyping = debounce(() => {
  this.props.doSearch(this.state.criteria);
  console.log(`doSearch(${this.state.criteria})`);
}, 300);

… and let’s call our new function from handleCriteriaChange:

handleCriteriaChange = (e: React.FormEvent<HTMLInputElement>) => {
  this.setState({ criteria: e.currentTarget.value }, () => {
    this.raiseDoSearchWhenUserStoppedTyping();
  });
};

… and let’s give this a try: search criteria component debounced … which is just what we want!

Here’s the full code for our debouncing search criteria component:

import * as React from "react";
import { debounce } from "lodash";

interface SearchCriteriaProps {
  placeholder: string;
  doSearch: (criteria: string) => void;
}
interface SearchCriteriaState {
  criteria: string;
}

class SearchCriteria extends React.Component<
  SearchCriteriaProps,
  SearchCriteriaState
> {
  raiseDoSearchWhenUserStoppedTyping = debounce(() => {
    this.props.doSearch(this.state.criteria);
    console.log(`doSearch(${this.state.criteria})`);
  }, 300);

  constructor() {
    super();

    this.state = {
      criteria: ""
    };
  }

  handleCriteriaChange = (e: React.FormEvent<HTMLInputElement>) => {
    this.setState({ criteria: e.currentTarget.value }, () => {
      this.raiseDoSearchWhenUserStoppedTyping();
    });
  };

  render() {
    return (
      <div className="input-group">
        <input
          type="text"
          placeholder={this.props.placeholder}
          value={this.state.criteria}
          onChange={this.handleCriteriaChange}
          className="form-control"
        />
      </div>
    );
  }
}

export default SearchCriteria;

If you to learn more about using TypeScript with React, you may find my course useful:

Using TypeScript with React

Using TypeScript with React
Find out more