Hone logo
Hone
Problems

Implement useDebounceCallback Hook in React

In React applications, it's common to trigger expensive operations (like API calls or heavy computations) in response to user input, such as typing in a search bar. However, repeatedly executing these operations on every keystroke can lead to performance issues and unnecessary network requests. The useDebounceCallback hook is a powerful tool to mitigate this by delaying the execution of a callback function until a specified amount of time has passed without the function being called again.

Problem Description

Your task is to implement a custom React hook called useDebounceCallback in TypeScript. This hook should accept a callback function and a delay time (in milliseconds) and return a debounced version of that callback.

Key Requirements:

  1. Debouncing Logic: The returned callback function should only execute after a certain period of inactivity (defined by the delay parameter). If the debounced callback is invoked again before the delay has elapsed, the previous timer should be reset, and a new timer should start.
  2. Callback Execution: When the delay does complete without further invocations, the original callback function should be executed.
  3. Argument Preservation: The debounced callback should accept any arguments and pass them correctly to the original callback function when it's finally executed.
  4. Cleanup: The hook should handle cleanup to prevent memory leaks. Specifically, if the component unmounts before the debounced callback is executed, the pending execution should be canceled.
  5. Type Safety: The hook should be strongly typed using TypeScript.

Expected Behavior:

  • When the returned debounced function is called, a timer is set.
  • If the debounced function is called again before the timer expires, the previous timer is cleared, and a new timer starts.
  • If the timer expires without the debounced function being called again, the original callback is executed with the arguments from the last invocation.
  • If the component using the hook unmounts, any pending debounced function call should be canceled.

Examples

Example 1: Basic Debouncing

Imagine a search input where we want to trigger an API search only after the user stops typing for 500ms.

import React, { useState, useEffect } from 'react';
import { useDebounceCallback } from './useDebounceCallback'; // Assume hook is in this file

function SearchComponent() {
  const [searchTerm, setSearchTerm] = useState('');
  const [apiResult, setApiResult] = useState<string | null>(null);

  // The function to be debounced
  const fetchSearchResults = (query: string) => {
    console.log(`Fetching results for: ${query}`);
    // In a real app, this would be an API call
    setApiResult(`Results for "${query}"`);
  };

  // Get the debounced version of fetchSearchResults
  // Delay of 500ms
  const debouncedFetch = useDebounceCallback(fetchSearchResults, 500);

  const handleInputChange = (event: React.ChangeEvent<HTMLInputElement>) => {
    const value = event.target.value;
    setSearchTerm(value);
    // Call the debounced function with the current input value
    debouncedFetch(value);
  };

  return (
    <div>
      <input
        type="text"
        placeholder="Search..."
        value={searchTerm}
        onChange={handleInputChange}
      />
      {apiResult && <p>{apiResult}</p>}
    </div>
  );
}

Explanation:

When the user types, handleInputChange is called on every keystroke. debouncedFetch is invoked with the current search term. If the user types quickly, debouncedFetch will be called multiple times within the 500ms delay. The useDebounceCallback hook ensures that fetchSearchResults is only actually called 500ms after the user stops typing. The apiResult will then update.

Example 2: Rapid Input and Unmount

Consider a scenario where the user types very quickly, and the component unmounts before the debounce delay finishes.

Scenario:

  1. User types 'a'. debouncedFetch('a') is called. Timer 1 starts (500ms).
  2. User types 'b' immediately after. debouncedFetch('b') is called. Timer 1 is cleared, Timer 2 starts (500ms).
  3. User types 'c' immediately after. debouncedFetch('c') is called. Timer 2 is cleared, Timer 3 starts (500ms).
  4. The component unmounts before Timer 3 finishes.

Expected Behavior: The fetchSearchResults function should not be called with 'c'. The timer should be cleared due to unmount.

Constraints

  • The delay parameter will be a non-negative integer representing milliseconds.
  • The callback function can accept zero or more arguments of any type.
  • The hook should be implemented using standard React hooks (useState, useEffect, useRef).
  • Performance: The debouncing mechanism should be efficient and not introduce significant overhead.
  • No external libraries are allowed for the core debouncing logic.

Notes

  • Think about how to store the timer ID.
  • Consider how to correctly handle the arguments passed to the original callback.
  • The useEffect hook with an empty dependency array [] is often useful for cleanup operations.
  • The useRef hook can be useful for storing values that persist across renders without causing re-renders.
  • Ensure the return type of the hook is correctly typed to reflect a function that accepts the same arguments as the original callback and returns void.
Loading editor...
typescript