CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-use-debounce

React hooks library for debouncing and throttling functionality with small footprint and comprehensive control features

Pending
Overview
Eval results
Files

callback-debouncing.mddocs/

Callback Debouncing

Callback debouncing with useDebouncedCallback creates debounced versions of functions that delay execution until after a specified wait time has passed since the last invocation. This is ideal for optimizing expensive operations like API calls, search queries, form submissions, and event handlers.

Capabilities

useDebouncedCallback Hook

Creates a debounced version of a callback function with comprehensive control options.

/**
 * Creates a debounced function that delays invoking func until after wait
 * milliseconds have elapsed since the last time the debounced function was
 * invoked, or until the next browser frame is drawn.
 *
 * @param func - The function to debounce
 * @param wait - The number of milliseconds to delay (defaults to requestAnimationFrame if 0 or omitted)
 * @param options - Optional configuration object
 * @returns Debounced function with control methods
 */
function useDebouncedCallback<T extends (...args: any) => ReturnType<T>>(
  func: T,
  wait?: number,
  options?: Options
): DebouncedState<T>;

Parameters:

  • func: T - The function to debounce
  • wait?: number - Wait time in milliseconds. If 0 or omitted, uses requestAnimationFrame in browser environments
  • options?: Options - Configuration object with:
    • leading?: boolean - If true, invokes the function on the leading edge of the timeout (default: false)
    • trailing?: boolean - If true, invokes the function on the trailing edge of the timeout (default: true)
    • maxWait?: number - Maximum time the function is allowed to be delayed before it's invoked
    • debounceOnServer?: boolean - If true, enables debouncing in server-side environments (default: false)

Returns:

  • DebouncedState<T> - Debounced function with control methods (cancel, flush, isPending)

Usage Examples:

import React, { useState } from 'react';
import { useDebouncedCallback } from 'use-debounce';

// Basic API call debouncing
function SearchComponent() {
  const [results, setResults] = useState([]);
  const [loading, setLoading] = useState(false);

  const debouncedSearch = useDebouncedCallback(
    async (searchTerm: string) => {
      if (!searchTerm.trim()) return;
      
      setLoading(true);
      try {
        const response = await fetch(`/api/search?q=${searchTerm}`);
        const data = await response.json();
        setResults(data.results);
      } catch (error) {
        console.error('Search failed:', error);
      } finally {
        setLoading(false);
      }
    },
    500
  );

  return (
    <div>
      <input
        type="text"
        onChange={(e) => debouncedSearch(e.target.value)}
        placeholder="Search..."
      />
      {loading && <p>Searching...</p>}
      <ul>
        {results.map((result) => (
          <li key={result.id}>{result.title}</li>
        ))}
      </ul>
    </div>
  );
}

// Leading edge execution
function QuickAction() {
  const debouncedAction = useDebouncedCallback(
    (action: string) => {
      console.log('Executing:', action);
      // Perform action immediately on first call
    },
    1000,
    { leading: true, trailing: false }
  );

  return (
    <button onClick={() => debouncedAction('quick-save')}>
      Quick Save
    </button>
  );
}

// With maxWait option
function AutoSave() {
  const [content, setContent] = useState('');

  const debouncedSave = useDebouncedCallback(
    async (data: string) => {
      console.log('Auto-saving...', data);
      await fetch('/api/save', {
        method: 'POST',
        body: JSON.stringify({ content: data }),
        headers: { 'Content-Type': 'application/json' }
      });
    },
    2000,
    { maxWait: 10000 } // Force save every 10 seconds maximum
  );

  React.useEffect(() => {
    debouncedSave(content);
  }, [content, debouncedSave]);

  return (
    <textarea
      value={content}
      onChange={(e) => setContent(e.target.value)}
      placeholder="Type here... (auto-saves)"
    />
  );
}

// Event handling with native listeners
function ScrollHandler() {
  const [position, setPosition] = useState(0);

  const debouncedScrollHandler = useDebouncedCallback(
    () => {
      setPosition(window.pageYOffset);
    },
    100
  );

  React.useEffect(() => {
    window.addEventListener('scroll', debouncedScrollHandler);
    return () => {
      window.removeEventListener('scroll', debouncedScrollHandler);
    };
  }, [debouncedScrollHandler]);

  return <div>Scroll position: {position}px</div>;
}

// Using control functions
function ControlledCallback() {
  const [message, setMessage] = useState('');

  const debouncedSubmit = useDebouncedCallback(
    (msg: string) => {
      console.log('Submitting:', msg);
      // Submit message
    },
    1000
  );

  const handleSubmit = () => {
    debouncedSubmit(message);
  };

  const handleCancel = () => {
    debouncedSubmit.cancel();
  };

  const handleFlush = () => {
    debouncedSubmit.flush();
  };

  return (
    <div>
      <input
        value={message}
        onChange={(e) => setMessage(e.target.value)}
      />
      <button onClick={handleSubmit}>Submit (Debounced)</button>
      <button onClick={handleCancel}>Cancel</button>
      <button onClick={handleFlush}>Submit Now</button>
      <p>Pending: {debouncedSubmit.isPending() ? 'Yes' : 'No'}</p>
    </div>
  );
}

// Form validation
function ValidatedForm() {
  const [form, setForm] = useState({ email: '', password: '' });
  const [errors, setErrors] = useState({});

  const debouncedValidate = useDebouncedCallback(
    (formData: typeof form) => {
      const newErrors: any = {};
      
      if (!formData.email.includes('@')) {
        newErrors.email = 'Invalid email';
      }
      
      if (formData.password.length < 8) {
        newErrors.password = 'Password too short';
      }
      
      setErrors(newErrors);
    },
    300
  );

  React.useEffect(() => {
    debouncedValidate(form);
  }, [form, debouncedValidate]);

  return (
    <form>
      <input
        type="email"
        value={form.email}
        onChange={(e) => setForm(prev => ({ ...prev, email: e.target.value }))}
      />
      {errors.email && <span>{errors.email}</span>}
      
      <input
        type="password"
        value={form.password}
        onChange={(e) => setForm(prev => ({ ...prev, password: e.target.value }))}
      />
      {errors.password && <span>{errors.password}</span>}
    </form>
  );
}

Return Value Handling

The debounced function returns the result of the last invocation. If there are no previous invocations, it returns undefined.

function ReturnValueExample() {
  const debouncedCalculate = useDebouncedCallback(
    (a: number, b: number) => {
      return a + b;
    },
    500
  );

  const handleCalculate = () => {
    // First call returns undefined (no previous invocation)
    const result = debouncedCalculate(5, 3);
    console.log('Immediate result:', result); // undefined
    
    // After the debounce period, subsequent calls return the last result
    setTimeout(() => {
      const cachedResult = debouncedCalculate(10, 7);
      console.log('Cached result:', cachedResult); // 8 (from previous call)
    }, 1000);
  };

  return <button onClick={handleCalculate}>Calculate</button>;
}

Server-Side Rendering

By default, debouncing is disabled in server-side environments. Enable it with the debounceOnServer option:

function SSRCompatible() {
  const debouncedCallback = useDebouncedCallback(
    (data: string) => {
      console.log('Processing:', data);
    },
    500,
    { debounceOnServer: true }
  );

  // This will work in both client and server environments
  return (
    <button onClick={() => debouncedCallback('data')}>
      Process
    </button>
  );
}

Options Interface

interface Options extends CallOptions {
  /** The maximum time the given function is allowed to be delayed before it's invoked */
  maxWait?: number;
  /** If set to true, all debouncing and timers will happen on the server side as well */
  debounceOnServer?: boolean;
}

interface CallOptions {
  /** Controls if the function should be invoked on the leading edge of the timeout */
  leading?: boolean;
  /** Controls if the function should be invoked on the trailing edge of the timeout */
  trailing?: boolean;
}

Common Patterns

API Rate Limiting

function RateLimitedAPI() {
  const debouncedRequest = useDebouncedCallback(
    async (endpoint: string, data: any) => {
      return fetch(endpoint, {
        method: 'POST',
        body: JSON.stringify(data),
        headers: { 'Content-Type': 'application/json' }
      });
    },
    1000 // Limit to one request per second
  );

  return {
    createUser: (userData: any) => debouncedRequest('/api/users', userData),
    updateUser: (userId: string, userData: any) => 
      debouncedRequest(`/api/users/${userId}`, userData)
  };
}

Expensive Calculations

function ExpensiveCalculation() {
  const [input, setInput] = useState('');
  const [result, setResult] = useState('');

  const debouncedCalculate = useDebouncedCallback(
    (value: string) => {
      // Simulate expensive calculation
      const processed = value
        .split('')
        .reverse()
        .map(char => char.charCodeAt(0))
        .reduce((sum, code) => sum + code, 0);
      
      setResult(`Calculated: ${processed}`);
    },
    500
  );

  React.useEffect(() => {
    if (input) {
      debouncedCalculate(input);
    }
  }, [input, debouncedCalculate]);

  return (
    <div>
      <input value={input} onChange={(e) => setInput(e.target.value)} />
      <p>{result}</p>
    </div>
  );
}

Cleanup on Unmount

function ComponentWithCleanup() {
  const debouncedSave = useDebouncedCallback(
    (data: string) => {
      // Save data
      console.log('Saving:', data);
    },
    1000
  );

  React.useEffect(() => {
    // Cleanup: flush any pending saves when component unmounts
    return () => {
      debouncedSave.flush();
    };
  }, [debouncedSave]);

  return (
    <input onChange={(e) => debouncedSave(e.target.value)} />
  );
}

Install with Tessl CLI

npx tessl i tessl/npm-use-debounce

docs

callback-debouncing.md

index.md

throttling.md

value-debouncing.md

tile.json