Logo

Debounced Input

An enhanced Input component that delays value updates, perfect for search fields and performance-sensitive forms.

Try typing below to see the debouncing effect.

Note

Check your console to see the debouncing effect.

Installation

Install Shadcn Input

This component is built on top of the Shadcn UI Input component. If you haven't installed it yet, run:

npx shadcn@latest add input

Create the Component

Create a new file named debounced-input.tsx in your components folder (e.g., components/ui/debounced-input.tsx) and copy the code below.

components/ui/debounced-input.tsx
"use client";

import { Input } from "@/components/ui/input";
import { cn } from "@/lib/utils";
import { useEffect, useState } from "react";

type DebouncedInputProps = {
    debouncing?: boolean;
    debouncingValue?: number;
    onDebouncedChange?: (value: string) => void;
    className?: string;
} & React.ComponentProps<typeof Input>;

const DebouncedInput = ({ debouncing, debouncingValue = 500, onDebouncedChange, className, ...props }: DebouncedInputProps) => {
    const isControlled = props.value !== undefined;

    // This is used so that user typing is not affected by the debouncing
    const [input, setInput] = useState(
        isControlled ?
        (props.value as string) :
        (props.defaultValue as string) ?? ""
    );

    useEffect(() => {
        if (isControlled) {
            setInput(props.value as string);
        }
    }, [props.value, isControlled]);

    useEffect(() => {
        if(!debouncing) return;

        // Set timer
        const timer = setTimeout(() => {
            onDebouncedChange?.(input);
        }, debouncingValue);

        // Clear timeout
        return () => clearTimeout(timer);
    }, [input, debouncing, debouncingValue]);

    return (
        <Input
            {...props}
            value={isControlled ? props.value : input}
            onChange={(e) => {
                if(!isControlled) {
                    setInput(e.target.value);
                }
                props.onChange?.(e);
            }}
            className={cn(className)}
        />
    )
}

export default DebouncedInput;

Usage

import DebouncedInput from "@/components/ui/debounced-input"

export function BasicExample() {
  return (
    <DebouncedInput 
      debouncing 
      debouncingValue={500}
      onDebouncedChange={(val) => console.log(val)}
      placeholder="Type safely..."
    />
  )
}
import { useState } from "react"
import DebouncedInput from "@/components/ui/debounced-input"

export function ControlledExample() {
  const [value, setValue] = useState("")

  return (
    <DebouncedInput 
        value={value}
        onChange={(e) => setValue(e.target.value)}
        debouncing
        onDebouncedChange={(val) => console.log("Final:", val)}
    />
  )
}

Props

The DebouncedInput accepts all props from the standard HTML input element (via Shadcn Input), plus the following:

PropTypeDefaultDescription
debouncingbooleanfalseEnables the debouncing functionality.
debouncingValuenumber500The delay in milliseconds before the onDebouncedChange callback is triggered.
onDebouncedChange(value: string) => void-Callback function triggered after the debounce delay. Receives the current input value.
classNamestring-Additional CSS classes to apply to the input.

Note

If debouncing is false, onDebouncedChange will not be called. You should use the standard onChange prop in that case, or ensure debouncing is true if you want the delayed callback.

Concepts

What is debouncing ?

Debouncing is a programming practice used to ensure that time-consuming tasks do not fire so often, making them inefficient. In the context of an Input, it limits the rate at which a function fires.

Without Debouncing

An API call or state update happens on every keystroke.

Input: "S" -> Call API
Input: "Se" -> Call API
Input: "Sea" -> Call API
Input: "Sear" -> Call API
Input: "Search" -> Call API
With Debouncing

The action waits until the user stops typing for a set duration.

Input: "S" ...
Input: "Se" ...
Input: "Sea" ...
Input: "Sear" ...
Input: "Search" -> Call API (Once)

On this page