Contents
In brief
Search boxes, filters, and autosave should not create a network request for every keystroke. In React, the practical solution is to separate the fast value shown in the UI from the work that should run after a short quiet period.
The Dev.to article examines two hooks from @reactuses/core. useDebounce produces a delayed copy of a value, while useDebounceFn delays a callback. Both remove repetitive timer handling and cover lifecycle details that hand-written code often misses.
What happened
A simple implementation usually starts with setTimeout in an input handler. It works in a short demo, then spreads into refs, timer clearing, and component-specific cleanup. Each copy is another chance to omit one of those details.
Two failures are especially common. A component can unmount while its pending request still fires, or a delayed function can read an old prop or state value captured by its closure. The bug may only appear after a navigation or a quick sequence of edits.
useDebounce(value, wait) addresses the value case. The input receives every new query immediately, while an effect watches debouncedQuery, which changes only after typing stops.
useDebounceFn(fn, wait) is for an action with arguments. It exposes run, cancel, and flush: schedule the action, discard the pending call, or execute it immediately.
Why it matters
Debouncing is more than a way to cut request volume. Search suggestions, address lookups, and validation can produce results for input that is already obsolete. On a slow connection, that extra work becomes visible and may even surface stale results.
Keeping interface state separate from the side effect preserves responsiveness. If the value bound to an <input> is delayed, typing feels broken. If only the search operation is delayed, the field remains immediate while the backend sees fewer, more useful requests.
Autosave has a different lifecycle need. When someone clicks Publish, flush can persist the pending draft before the main operation begins. When they discard it, cancel prevents an old timer from saving content after the decision.
The hooks are also safe during server rendering: they do not touch browser globals while React renders. That makes them suitable for server-rendered components without extra window guards or hydration surprises.
In practice
Start by deciding whether the application needs a delayed value or a delayed command. That choice matters more than the package itself because it determines how state and effects are organised in the component.
For a search field, keep two values: the immediate text used by the interface and a delayed version used by useEffect. Do not issue a request for an empty delayed value, and handle cancellation of the network request separately when needed.
- Use
useDebouncefor search terms, filters, and slider values that other code reads. - Use
useDebounceFnfor autosave, telemetry, and direct network actions that receive arguments. - Call
cancelwhen a pending action is explicitly discarded; callflushbefore submitting a form or an operation requiring current data. - Keep UI state fast: delay the side effect rather than the input’s visual update.
- Consider
maxWaitfor long uninterrupted activity, so an update still occurs at a bounded interval.
The leading and trailing options control the first and final call. leading: true can respond to the first click immediately while absorbing rapid repeats; trailing: true is the natural fit for a search that should start after typing ends.
Do not confuse debounce with throttling. Scroll position, drag coordinates, and a live progress readout usually need useThrottle or useThrottleFn, because they should update at a steady cadence during continuous activity.
Takeaway
A manual setTimeout in React requires more care than it first appears: timers must be cleaned up, callbacks must see current data, and no work should survive a component that has gone away. A shared hook centralises those rules.
Choose useDebounce when consumers need a delayed state value, and useDebounceFn when the operation itself should wait. Keeping those two cases distinct makes search interfaces more responsive, autosave more reliable, and components easier to maintain.

