How do you perform debounce in React.js?
I want to debounce the handleOnChange.
I tried with debounce(this.handleOnChange, 200)
but it doesn\'t
Just another variant with recent react and lodash.
class Filter extends Component {
static propTypes = {
text: PropTypes.string.isRequired,
onChange: PropTypes.func.isRequired
}
state = {
initialText: '',
text: ''
}
constructor (props) {
super(props)
this.setText = this.setText.bind(this)
this.onChange = _.fp.debounce(500)(this.onChange.bind(this))
}
static getDerivedStateFromProps (nextProps, prevState) {
const { text } = nextProps
if (text !== prevState.initialText) {
return { initialText: text, text }
}
return null
}
setText (text) {
this.setState({ text })
this.onChange(text)
}
onChange (text) {
this.props.onChange(text)
}
render () {
return (<input value={this.state.text} onChange={(event) => this.setText(event.target.value)} />)
}
}
I was searching for a solution to the same problem and came across this thread as well as some others but they had the same problem: if you are trying to do a handleOnChange
function and you need the value from an event target, you will get cannot read property value of null
or some such error. In my case, I also needed to preserve the context of this
inside the debounced function since I'm executing a fluxible action. Here's my solution, it works well for my use case so I'm leaving it here in case anyone comes across this thread:
// at top of file:
var myAction = require('../actions/someAction');
// inside React.createClass({...});
handleOnChange: function (event) {
var value = event.target.value;
var doAction = _.curry(this.context.executeAction, 2);
// only one parameter gets passed into the curried function,
// so the function passed as the first parameter to _.curry()
// will not be executed until the second parameter is passed
// which happens in the next function that is wrapped in _.debounce()
debouncedOnChange(doAction(myAction), value);
},
debouncedOnChange: _.debounce(function(action, value) {
action(value);
}, 300)
for throttle
or debounce
the best way is to create a function creator so you can use it any where, for example:
updateUserProfileField(fieldName) {
const handler = throttle(value => {
console.log(fieldName, value);
}, 400);
return evt => handler(evt.target.value.trim());
}
and in your render
method you can do:
<input onChange={this.updateUserProfileField("givenName").bind(this)}/>
the updateUserProfileField
method will create a separated function each time you call it.
Note don't try to return the handler directly for example this will not work:
updateUserProfileField(fieldName) {
return evt => throttle(value => {
console.log(fieldName, value);
}, 400)(evt.target.value.trim());
}
the reason why this will not work because this will generate a new throttle function each time the event called instead of using the same throttle function, so basically the throttle will be useless ;)
Also if you use debounce
or throttle
you don't need setTimeout
or clearTimeout
, this is actually why we use them :P
Here's a snippet using @Abra's approach wrapped in a function component (we use fabric for the UI, just replace it with a simple button)
import React, { useCallback } from "react";
import { debounce } from "lodash";
import { PrimaryButton, DefaultButton } from 'office-ui-fabric-react/lib/Button';
const debounceTimeInMS = 2000;
export const PrimaryButtonDebounced = (props) => {
const debouncedOnClick = debounce(props.onClick, debounceTimeInMS, { leading: true });
const clickHandlerDebounced = useCallback((e, value) => {
debouncedOnClick(e, value);
},[]);
const onClick = (e, value) => {
clickHandlerDebounced(e, value);
};
return (
<PrimaryButton {...props}
onClick={onClick}
/>
);
}
A nice and clean solution, that doesn't require any external dependencies:
Debouncing with React Hooks
It uses a custom plus the useEffect React hooks and the setTimeout
/ clearTimeout
method.
My solution is hooks based (written in Typescript).
I've got 2 main hooks useDebouncedValue
and useDebouncedCallback
First - useDebouncedValue
Let's say we've got a search box, but we want to ask the server for search results after the user has stopped typing for 0,5s
function SearchInput() {
const [realTimeValue, setRealTimeValue] = useState('');
const debouncedValue = useDebouncedValue(realTimeValue, 500); // this value will pick real time value, but will change it's result only when it's seattled for 500ms
useEffect(() => {
// this effect will be called on seattled values
api.fetchSearchResults(debouncedValue);
}, [debouncedValue])
return <input onChange={event => setRealTimeValue(event.target.value)} />
}
Implementation
import { useState, useEffect } from "react";
export function useDebouncedValue<T>(input: T, time = 500) {
const [debouncedValue, setDebouncedValue] = useState(input);
// every time input value has changed - set interval before it's actually commited
useEffect(() => {
const timeout = setTimeout(() => {
setDebouncedValue(input);
}, time);
return () => {
clearTimeout(timeout);
};
}, [input, time]);
return debouncedValue;
}
Second useDebouncedCallback
It just creates a 'debounced' function in the scope of your component.
Let's say we've got a component with a button that will show alert 500ms after you stopped clicking it.
function AlertButton() {
function showAlert() {
alert('Clicking has seattled');
}
const debouncedShowAlert = useDebouncedCallback(showAlert, 500);
return <button onClick={debouncedShowAlert}>Click</button>
}
Implementation (note I'm using lodash/debounce as a helper)
import debounce from 'lodash/debounce';
import { useMemo } from 'react';
export function useDebouncedCallback<T extends (...args: any) => any>(callback: T, wait?: number) {
const debouncedCallback = useMemo(() => debounce(callback, wait), [callback, wait]);
return debouncedCallback;
}