How to start search only when user stops typing?

ReactjsSettimeout

Reactjs Problem Overview


I need to perform a Search when user stops typing.I know I am supposed to use setTimeout() . But with Reactjs I cant find how it works. Can someone please tell me how to invoke a method (that will handle Search) when the user stops typing for a few seconds (suppose 5).I cant figure out where to write the code to check that the user has stopped typing.

import React, {Component, PropTypes} from 'react';

export default class SearchBox extends Component {

    state={
      name:" ",
    }

    changeName = (event) => {
        this.setState({name: event.target.value}); 
    }

    sendToParent = () => {
        this.props.searching(this.state.name);
    }

    render() {
        return (
            <div>
                 <input type="text"  placeholder='Enter name you wish to Search.'  onChange={this.changeName} />
              
            </div>
        );
    }
}   

I want to invoke the sendToParent method when the user stops typing.

Reactjs Solutions


Solution 1 - Reactjs

Implement using useEffect hook:

function Search() {
  const [searchTerm, setSearchTerm] = useState('')

  useEffect(() => {
    const delayDebounceFn = setTimeout(() => {
      console.log(searchTerm)
      // Send Axios request here
    }, 3000)
    
    return () => clearTimeout(delayDebounceFn)
  }, [searchTerm])

  return (
    <input
      autoFocus
      type='text'
      autoComplete='off'
      className='live-search-field'
      placeholder='Search here...'
      onChange={(e) => setSearchTerm(e.target.value)}
    />
  )
}

Solution 2 - Reactjs

You can use setTimeout with respect to your code as follows,

state = {
    name: '',
    typing: false,
    typingTimeout: 0
}
changeName = (event) => {
    const self = this;

    if (self.state.typingTimeout) {
       clearTimeout(self.state.typingTimeout);
    }

    self.setState({
       name: event.target.value,
       typing: false,
       typingTimeout: setTimeout(function () {
           self.sendToParent(self.state.name);
         }, 5000)
    });
}

Also, you need to bind changeName handler function in constructor.

constructor(props) {
   super(props);
   this.changeName = this.changeName.bind(this);
}

Solution 3 - Reactjs

Another way that worked with me:

class Search extends Component {
  constructor(props){
    super(props);
    this.timeout =  0;
  }

  doSearch(evt){
  	var searchText = evt.target.value; // this is the search text
    if(this.timeout) clearTimeout(this.timeout);
  	this.timeout = setTimeout(() => {
	  //search function
	}, 300);
  }

   render() {
    return (
      <div className="form-group has-feedback">
      	<label className="control-label">Any text</label>
      	<input ref="searchInput" type="text" onChange={evt => this.doSearch(evt)} />
      </div>
    );
  }
}

Solution 4 - Reactjs

I used the debounce function of lodash

onChangeSearchInput = (evt)=> {
    this.debouncedSearch(evt.target.value);
};

debouncedSearch = debounce(function (query) {
    this.setState({query});
}, 1000);

Somewhere in my render method i have this input field

<input
    type='text'
    onChange={this.onChangeSearchInput}
    className='uk-input'
    placeholder={'search by name or email...'}
   />

Solution 5 - Reactjs

This library (use-debounce) is nice and simple.

Setup

yarn add use-debounce

or

npm i use-debounce --save

Usage sample from documentation

import React, { useState } from 'react';
import { useDebounce } from 'use-debounce';

export default function Input() {
  const [text, setText] = useState('Hello');
  const [value] = useDebounce(text, 1000);

  return (
    <div>
      <input
        defaultValue={'Hello'}
        onChange={(e) => {
          setText(e.target.value);
        }}
      />
      <p>Actual value: {text}</p>
      <p>Debounce value: {value}</p>
    </div>
  );
}

> Things that I liked at this moment, things could be different in > future!: > > - Easy to setup & use > - Less Boilerplate code > - Modest ratings (~1K) and usage (npm - 200K downloads/Week) > - Supports timeout, MaxWait and other features

Solution 6 - Reactjs

I think we can do it in a more simpler and cleaner manner, without abrupting the state parameter which calls the complete component life cycle like this:

constructor(props) {
    super(props);
    
    //Timer
    this.typingTimeout = null;

    //Event
    this.onFieldChange = this.onFieldChange.bind(this);

    //State
    this.state = { searchValue: '' }; 
}   


 /**
 * Called on the change of the textbox.
 * @param  {[Object]} event [Event object.]
 */
onFieldChange(event) {
    // Clears the previously set timer.
    clearTimeout(this.typingTimeout);

    // Reset the timer, to make the http call after 475MS (this.callSearch is a method which will call the search API. Don't forget to bind it in constructor.)
    this.typingTimeout = setTimeout(this.callSearch, 475);

    // Setting value of the search box to a state.
    this.setState({ [event.target.name]: event.target.value });
}


<div className="block-header">
     <input
           type="text"
           name="searchValue"
           value={this.state.searchValue}
           placeholder="User Name or Email"
           onChange={this.onFieldChange}
     />
</div>

Solution 7 - Reactjs

I have use this custom hook and it's work perfectly no issue still.

export function useSearchDebounce(delay = 350) {
  const [search, setSearch] = useState(null);
  const [searchQuery, setSearchQuery] = useState(null);

  useEffect(() => {
    const delayFn = setTimeout(() => setSearch(searchQuery), delay);
    return () => clearTimeout(delayFn);
  }, [searchQuery, delay]);

  return [search, setSearchQuery];
}

Use in any place like

const [search, setSearch] = useSearchDebounce();

<input onChange={(e) => setSearch(e.target.value)}/>

Solution 8 - Reactjs

How about a custom hook?

import {useEffect, useRef, useState} from "react";

export default function useSearchInputState(searchHandler) {
  
  // to prevent calling the handler on component mount
  const didMountRef = useRef(false);

  const [searchValue, setSearchValue] = useState(null);

  useEffect(() => {
    let delayDebounceFn;

    if (didMountRef.current) {
      delayDebounceFn = setTimeout(searchHandler, 600)
    } else {
      didMountRef.current = true;
    }

    return () => clearTimeout(delayDebounceFn);
  }, [searchValue]); // eslint-disable-line react-hooks/exhaustive-deps

  return [searchValue, setSearchValue];

}

Usage:

function MyComponent(props) {

  const [searchValue, setSearchValue] = useSearchInputState(() => {
    resetData(searchValue ?? null, selectedFilterPos); // replace with your code
  });

  return (
    <input className="Search"
           onChange={e => setSearchValue(e?.target?.value ?? null)}
      />
  );
}

Solution 9 - Reactjs

you can just use the debounce from lodash or simulate using setTimeout.

import React, {Component, PropTypes} from 'react';

export default class SearchBox extends Component {
    constructor(props){
       super(props);
       this.state={ name:" "}
       this.timeout =  null;

    }

    changeName = (event) => {
        clearTimeout(timeout);
         if(timeout){
           setTimeout((event)=> this.setState({name: event.target.value}), 200)
         }
    }

    sendToParent = () => {
        this.props.searching(this.state.name);
    }

    render() {
        return (
            <div>
                 <input type="text"  placeholder='Enter name you wish to Search.'  onChange={this.changeName} />

            </div>
        );
    }
}

Solution 10 - Reactjs

you can use react hooks useEffect with the use of setTimeOut function since it always return the timer id and you could easily clear the timer with that id as follows

export const Search = () => {
const [term, setTerm] = useState();
const [results, setResult] = useState([]);

useEffect(() => {
	const searchWiki = async () => {
		const { data } = await axios.get('https://en.wikipedia.org/w/api.php', {
			params: {
				srsearch: term,
			},
		});

		setResult(data.query.search);
	};
	const timerId = setTimeout(() => {
		searchWiki();
     // make a request after 1 second since there's no typing 
	}, 1000);

	return () => {
		clearTimeout(timerId);
	};
}, [term]);

Solution 11 - Reactjs

For React hooks:

First we'll define a component

import React, { useEffect, useState } from "react";

const SearchInputText = ({ value, name, placeholder, onChange }) => {
  // state for keepign search text 
  const [searchText, setSearchText] = useState(value);
  // state for keeping the timeout
  const [searchTextTimeout, setSearchTextTimeout] = useState(null);

  // handler for form submit (pressing enter without waiting for setimeout to trigger)
  const handleSubmit = (e) => {
    e.preventDefault();
    // clear timeout as it'll that would be triggered
    if (searchTextTimeout) {
      clearTimeout(searchTextTimeout);
    }
    onChange(searchText);
  };

  // onChange handler
  const handleOnChange = (e) => {
  // cancelling previous timeouts
    if (searchTextTimeout) {
      clearTimeout(searchTextTimeout);
    }
    // first update the input text as user type
    setSearchText(e.target.value);
    // initialize a setimeout by wrapping in our searchTextTimeout so that we can clear it out using clearTimeout
    setSearchTextTimeout(
      setTimeout(() => {
        onChange(searchText);
        // timeout is 2500ms, change it to less or more.
      }, 2500),
    );
  };

  // making sure that we clear the timeout if/when the component unmount
  useEffect(() => {
    return () => clearTimeout(searchTextTimeout);
  }, [searchTextTimeout]);

  return (
    <form onSubmit={handleSubmit}>
      <input
        name={name}
        placeholder={placeholder}
        type="text"
        value={searchText}
        onChange={handleOnChange}
      />
    </form>
  );
};

export default SearchInputText;

Usage:

const Parent = () => {
  const handleChange = (e) => {
    // your implementation here
  };
  return (
    <div>
      <SortSearchInput name="search" placeholder="Enter Search" onChange={handleChange} />
    </div>
  );
};

Solution 12 - Reactjs

I made my own custom component like this.

import React, { useState, useEffect } from 'react'

const InputDebounce = props => {
  const { onChange, ...otherProps } = props

  const [inputTimeout, setInputTimeout] = useState(null)

  useEffect(() => () => clearTimeout(inputTimeout), [inputTimeout])

  const inputOnChange = value => {
    if (inputTimeout) clearTimeout(inputTimeout)
    setInputTimeout(
      setTimeout(() => {
        if (onChange) onChange(value)
      }, 1000)
    )
  }

  return (
    <input
      {...otherProps}
      onChange={e => inputOnChange(e.target.value)}
    />
  )
}

export default InputDebounce

And using anywhere like this.

import React from 'react'
import ReactDOM from 'react-dom'

import InputDebounce from './InputDebounce'

const App = () => {
  const usernameOnChange = value => {
    console.log(value)
  }

  return (
    <div>
      <InputDebounce
        type='text'
        name='username'
        placeholder='Username'
        onChange={usernameOnChange}
      />
    </div>
  )
}

ReactDOM.render(<App />, document.getElementById('root'))

Solution 13 - Reactjs

Here's a working component template with some useful parameters to get your started.

import React, { Component } from 'react'

const initialState = { results: [], value: '' }

export default class SearchBox extends Component {
  state = initialState
  timeout = null
  search_url = "https://example.com/search?q="
  min_query_length = 2
  timeout_duration = 300

  handleSearchChange = (e) => {
    let value = e.target.value
    clearTimeout(this.timeout);
    if (value.length < 1) {
        return this.setState(initialState) 
    } else {
        this.setState({ value })
        if (value.length>=this.min_query_length) {    
            this.timeout = setTimeout(this.search, this.timeout_duration);
        }
    }
  }

  search = () => {
    // assuming your results are returned as JSON
    fetch(`${this.search_url}${this.state.value}`)
    .then(res => res.json())
    .then(data => {
        this.setState({
            results: data,
        })
    })
  }

  render() {
    return (
          <input
            onChange={this.handleSearchChange}
          />
    )
  }
}

Solution 14 - Reactjs

using react hooks, modified from @anoNewb's answer. With additions:

  • prevent multiple triggers when there's still timer running
  • add on Form Submit event

codesandbox

    import React, { useState, useEffect } from "react";

    export default function App() {
      const [search, setSearch] = useState("");
      const [searchTimeout, setSearchTimeout] = useState(null);

      useEffect(() => {
        if (searchTimeout) {
          clearTimeout(searchTimeout);
        }

        setSearchTimeout(
          setTimeout(() => {
            loadUsers();
          }, 1000),
        );

        return () => clearTimeout(searchTimeout);
      }, [search]);

      const loadUsers = () => {
        console.log("axios call with query: ", search);
      };

      return (
        <div className="App">
          <form
            onSubmit={(e) => {
              e.preventDefault();
              if (searchTimeout) {
                clearTimeout(searchTimeout);
              }
              loadUsers();
            }}
          >
            <input
              onChange={(e) => {
                setSearch(e.target.value);
              }}
            />
          </form>
        </div>
      );
    }

Solution 15 - Reactjs

The code below works for me.

const[isReady, setReady]  = useState(true);
const onSearchSet =(event:React.ChangeEvent<HTMLInputElement>) => { 

    setCriteria(event.target.value);
    if(isReady) {
        setReady(false);
        const delayDebounceFn = setTimeout(() => {
            // Send Axios request here
            
            props.returnCall(props.RDropID, sortCriteria, event.target.value);

            setReady(true);
          }, 1000)
        
    }
      
};

Solution 16 - Reactjs

User lodash javascript library and use [_.debounce][1]

changeName: _.debounce(function (val) {
  console.log(val)                
}, 1000)

Solution 17 - Reactjs

Problem of Typeahead library https://twitter.github.io/typeahead.js/

Since the case here is simple, I can use a quick and dirty solution:

onChange: (event) ->
  if @_timeoutTask?
    clearTimeout @_timeoutTask

  @_timeoutTask = setTimeout (=>
    @sendToParent event.target.value
    clearTimeout @_timeoutTask
  ), 5000

In this way, the task will be triggered 5s after input event. If new event happens, the old task will be cancelled and a new task is scheduled, then it's another 5s to wait.

The difference in React is the where to store the computation state like _timeoutTask. The file scope, the component state, or the component instance.

Since _timeoutTask is component level, it should be be store globally. And it does not affect rendering, so not in component state too. So I suggest attaching it to component instance directly.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionshiniteView Question on Stackoverflow
Solution 1 - ReactjsHarshalView Answer on Stackoverflow
Solution 2 - ReactjsSaba HassanView Answer on Stackoverflow
Solution 3 - ReactjsNinh NgoView Answer on Stackoverflow
Solution 4 - ReactjsAkinola OlayinkaView Answer on Stackoverflow
Solution 5 - ReactjsstayingcoolView Answer on Stackoverflow
Solution 6 - ReactjsRahulView Answer on Stackoverflow
Solution 7 - ReactjsHiren BhutView Answer on Stackoverflow
Solution 8 - ReactjsDrunken DaddyView Answer on Stackoverflow
Solution 9 - ReactjsKhalid AzamView Answer on Stackoverflow
Solution 10 - ReactjsMena AzizView Answer on Stackoverflow
Solution 11 - ReactjsJawwadView Answer on Stackoverflow
Solution 12 - ReactjsozgrozerView Answer on Stackoverflow
Solution 13 - ReactjsinfiniteloopView Answer on Stackoverflow
Solution 14 - ReactjsIvan Rusli McdohlView Answer on Stackoverflow
Solution 15 - ReactjsplutomusangView Answer on Stackoverflow
Solution 16 - ReactjsAmir Ur RehmanView Answer on Stackoverflow
Solution 17 - ReactjsjiyinyiyongView Answer on Stackoverflow