Skip to main content

@kreisler/debounce

A powerful debounce implementation with advanced features like immediate execution, lifecycle hooks, and flood protection.

Installation

npm install @kreisler/debounce

Overview

Debouncing is a technique to limit how often a function executes by delaying its execution until after a specified time has passed since the last call. This is particularly useful for:
  • Search input fields
  • Window resize handlers
  • Scroll event handlers
  • API calls triggered by user input
  • Auto-save functionality
This package extends basic debouncing with lifecycle hooks, immediate execution options, and flood detection.

Basic Usage

import { debounce } from '@kreisler/debounce';

// Create a debounced function
const debouncedLog = debounce(console.log, 1000);

// Call it multiple times
debouncedLog('Hello'); // Won't execute immediately
debouncedLog('World'); // Won't execute immediately
debouncedLog('!');     // Will execute after 1000ms with '!'

API Reference

debounce

Creates a debounced version of the provided function.
function debounce(
  func: Function,
  msWait: number,
  fns?: Partial<TFns>
): Function
func
Function
required
The function to debounce
msWait
number
required
The number of milliseconds to wait before executing the function
fns
TFns
Optional configuration object with lifecycle hooks and settings
immediate
boolean
default:false
If true, the function will be called immediately on the first call, then debounced for subsequent calls
onCall
Function
Callback function executed every time the debounced function is called
onComplete
Function
Callback function executed when the debounced function finally executes
flood
number
Number of calls that trigger the onFlood callback. For example, if set to 5, onFlood will be called every 5 attempts
onFlood
Function
Callback function executed when the flood limit is reached

TFns Interface

interface TFns {
  immediate: boolean;
  onCall: Function;
  onComplete: Function;
  flood: number;
  onFlood: Function;
}

Examples

Simple Debounce

import { debounce } from '@kreisler/debounce';

const handleSearch = (query) => {
  console.log('Searching for:', query);
  // API call here
};

const debouncedSearch = debounce(handleSearch, 500);

// User types quickly
debouncedSearch('a');
debouncedSearch('ab');
debouncedSearch('abc');
// Only 'abc' will be searched after 500ms

Immediate Execution

Execute the function immediately on the first call, then debounce subsequent calls:
import { debounce } from '@kreisler/debounce';

const saveData = (data) => {
  console.log('Saving:', data);
};

const debouncedSave = debounce(saveData, 1000, {
  immediate: true
});

// First call executes immediately
debouncedSave('First call');  // Logs immediately

// Subsequent calls are debounced
debouncedSave('Second call');
debouncedSave('Third call');  // Logs after 1000ms

Lifecycle Hooks

Track when the debounced function is called and completed:
import { debounce } from '@kreisler/debounce';

const processInput = (value) => {
  console.log('Processing:', value);
};

const debouncedProcess = debounce(processInput, 500, {
  onCall: (value) => {
    console.log('Function called with:', value);
  },
  onComplete: (value) => {
    console.log('Processing complete for:', value);
  }
});

debouncedProcess('test');
// Logs: "Function called with: test"
// After 500ms logs: "Processing: test"
// Then logs: "Processing complete for: test"

Flood Protection

Detect when the function is being called too frequently:
import { debounce } from '@kreisler/debounce';

let attemptCount = 0;

const submitForm = (data) => {
  console.log('Submitting form:', data);
};

const debouncedSubmit = debounce(submitForm, 1000, {
  flood: 5,
  onFlood: (data) => {
    console.warn('Slow down! Too many attempts.');
    // Show user notification
  }
});

// Rapid calls
for (let i = 0; i < 10; i++) {
  debouncedSubmit({ attempt: i });
}
// onFlood will be called at attempts 5 and 10

Complete Example with All Features

import { debounce } from '@kreisler/debounce';

const apiCall = (searchTerm) => {
  console.log('Making API call for:', searchTerm);
  return fetch(`/api/search?q=${searchTerm}`);
};

const debouncedSearch = debounce(apiCall, 1000, {
  immediate: true,
  onCall: (term) => {
    console.log('Search initiated for:', term);
    // Show loading indicator
  },
  onComplete: (term) => {
    console.log('Search completed for:', term);
    // Hide loading indicator
  },
  flood: 7,
  onFlood: (term) => {
    console.warn('You are searching too quickly!');
    // Show rate limit warning
  }
});

// Usage in a search component
const searchInput = document.querySelector('#search');
searchInput.addEventListener('input', (e) => {
  debouncedSearch(e.target.value);
});

Common Use Cases

import { debounce } from '@kreisler/debounce';

const searchAPI = async (query) => {
  const response = await fetch(`/api/search?q=${query}`);
  return response.json();
};

const debouncedSearch = debounce(searchAPI, 300);

document.querySelector('#search').addEventListener('input', (e) => {
  debouncedSearch(e.target.value);
});
import { debounce } from '@kreisler/debounce';

const handleResize = () => {
  console.log('Window resized to:', window.innerWidth, window.innerHeight);
  // Recalculate layout
};

const debouncedResize = debounce(handleResize, 250);

window.addEventListener('resize', debouncedResize);
import { debounce } from '@kreisler/debounce';

const saveFormData = (formData) => {
  localStorage.setItem('draft', JSON.stringify(formData));
  console.log('Form auto-saved');
};

const debouncedSave = debounce(saveFormData, 2000, {
  onComplete: () => {
    // Show "Saved" indicator
    document.querySelector('#save-status').textContent = 'Saved';
  }
});

document.querySelector('form').addEventListener('input', () => {
  const formData = new FormData(document.querySelector('form'));
  debouncedSave(Object.fromEntries(formData));
});
import { debounce } from '@kreisler/debounce';

const checkScrollPosition = () => {
  const scrollPercent = (window.scrollY / document.body.scrollHeight) * 100;
  console.log('Scroll position:', scrollPercent + '%');
};

const debouncedScroll = debounce(checkScrollPosition, 100);

window.addEventListener('scroll', debouncedScroll);

Best Practices

1

Choose the Right Delay

  • 50-100ms: For smooth UI updates (e.g., scroll, drag)
  • 200-300ms: For search/autocomplete
  • 500-1000ms: For API calls, validation
  • 1000ms+: For auto-save, expensive operations
2

Use Immediate When Appropriate

Set immediate: true when you want the first interaction to be instant, with debouncing only for subsequent rapid calls.
3

Implement Flood Protection

Use the flood option to detect and warn users about excessive function calls, improving UX and preventing abuse.
4

Clean Up Event Listeners

Remember to remove event listeners when components unmount to prevent memory leaks:
const debouncedHandler = debounce(handler, 300);
element.addEventListener('input', debouncedHandler);

// Cleanup
element.removeEventListener('input', debouncedHandler);
Debounced functions maintain state across calls. Create a new debounced function for each independent use case to avoid shared state issues.

Type Exports

import type { TArrowFunction, TFns } from '@kreisler/debounce';

// TArrowFunction: (...args: any[]) => any
// TFns: Interface with all configuration options

License

MIT License - see the LICENSE file for details.

Build docs developers (and LLMs) love