How to start search only when user stops typing?
ReactjsSettimeoutReactjs 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
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.