What is debounce? Debounce is a way to delay a function until an action stops. For example, in a search bar, instead of running a search every time you press a key, debouncing waits until you finish typing, then runs the search just once. This keeps the app fast by avoiding too many repeated actions.
But we already have a way to debounce state either using hook libraries like useDebounce
or creating your own. So, why do we need this?
Let’s step back and see how useDebounce
work:
import React, { useState } from "react";
import { useDebounce } from "some-where";
const App = () => {
const [text, setText] = useState("");
const debouncedText = useDebounce(text, 1000); // 1000ms
// do something with this `debouncedText`
return (
<input
type="text"
value={text}
onChange={(e) => setText(e.target.value)}
/>
);
};
export default App;
With each keystroke, we update the state with setText
, and when the user stops typing for 1000ms, debouncedText
finally updates. Ideally, this is when we’d make an API call.
Now, here’s the problem: even though debouncedText
only updates once, the text
state still updates on each keystroke, causing the entire app to re-render on every input. This might not be an issue in this example, where we're just rendering a simple input element.
Lets take a real world example:
The code might look something like this:
import React, { useState } from "react";
import { useQuery } from 'react-query';
import { useDebounce } from "some-where";
const UserListPage = () => {
const [state, setState] = useState(initialState);
const debouncedState = useDebounce(state, 1000); // 1000ms
const { data } = useQuery({
queryKey: ['user', debouncedState],
queryFn: () => fetchUserList(debouncedState),
});
return (
<div>
<UserFilter state={state} setState={setState} />
<UserList data={data} />
</div>
);
};
export default UserListPage;
Now, do you see the problem? On each keystroke in any of the input fields, everything has to re-render, including the UserList
component, even though nothing related to it has changed.
Now, this is where <DebounceControl />
comes into play.
import React, { useState } from "react";
import DebounceControl from "debounce-control";
const App = () => {
const [text, setText] = useState("");
const onDebouncedChange = (value) => {
setText(value);
}
return (
<DebounceControl
value={text}
delay={1000}
onDebouncedChange={onDebouncedChange}
render={({ value, onChange }) => (
<input
type="text"
value={value}
onChange={(e) => onChange(e.target.value)}
/>
)}
/>
);
};
export default App;
Notice how DebounceControl
decouples the debouncing logic from the rest of the app. With this, the text
state only updates after debouncing occurs, and in the previous example, the UserList
or the entire component will no longer re-render on each keystroke.
Big question now. Why should we use this?
- Minimal re-renders
- Works with any form element (not limited to
input
ortextarea
). Also, the element is right there if you want to add aref
or modify props. - Type-safe
Demo:
Input component
Slider component
Resizable component
NPM: https://www.npmjs.com/package/debounce-control
Conclusion:
By using <DebounceControl />
, you can easily debounce any form element while keeping your app performant and type-safe.
Top comments (0)