# useDebounce

## Description

A React hook that debounces callback functions, preventing them from being called too frequently. This is particularly useful for optimizing performance in scenarios like search inputs, API calls, window resize handlers, or any function that might be triggered rapidly. The hook provides both a debounced version of the callback and a cancel function to clear pending executions.

## Aliases

- useDebounce
- Debounce Hook
- Throttled Callback Hook
- Delayed Execution Hook

## Hook Signature

```tsx
function useDebounce<T extends (...args: any[]) => void>(
  callback: T,
  delay: number
): {
  debouncedCallback: (...args: Parameters<T>) => void;
  cancel: () => void;
}
```

## Parameters

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `callback` | `T extends (...args: any[]) => void` | Yes | The function to debounce |
| `delay` | `number` | Yes | The debounce delay in milliseconds |

## Return Value

Returns an object with:
- **`debouncedCallback`**: The debounced version of the callback function
- **`cancel`**: Function to cancel any pending execution

## Examples

### Basic Search Input Debouncing
```tsx
import React, { useState } from 'react';
import { useDebounce, Input } from '@delightui/components';

function SearchExample() {
  const [query, setQuery] = useState('');
  const [results, setResults] = useState([]);
  const [loading, setLoading] = useState(false);

  const performSearch = async (searchQuery: string) => {
    if (!searchQuery.trim()) return;
    
    setLoading(true);
    try {
      const response = await fetch(`/api/search?q=${encodeURIComponent(searchQuery)}`);
      const data = await response.json();
      setResults(data.results);
    } finally {
      setLoading(false);
    }
  };

  const { debouncedCallback: debouncedSearch } = useDebounce(performSearch, 300);

  const handleInputChange = (value: string) => {
    setQuery(value);
    debouncedSearch(value);
  };

  return (
    <div>
      <Input
        value={query}
        onValueChange={handleInputChange}
        placeholder="Search..."
      />
      {loading && <div>Searching...</div>}
      <div>
        {results.map(result => (
          <div key={result.id}>{result.title}</div>
        ))}
      </div>
    </div>
  );
}
```

### Form Validation with Debouncing
```tsx
function FormValidationExample() {
  const [formData, setFormData] = useState({
    username: '',
    email: ''
  });
  const [errors, setErrors] = useState({});
  const [validating, setValidating] = useState(false);

  const validateForm = async (data: typeof formData) => {
    setValidating(true);
    try {
      const response = await fetch('/api/validate', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(data)
      });
      const validation = await response.json();
      setErrors(validation.errors || {});
    } finally {
      setValidating(false);
    }
  };

  const { debouncedCallback: debouncedValidate } = useDebounce(validateForm, 500);

  const updateField = (field: string, value: string) => {
    const newData = { ...formData, [field]: value };
    setFormData(newData);
    debouncedValidate(newData);
  };

  return (
    <Form>
      <FormField 
        name="username" 
        label="Username" 
        invalid={!!errors.username}
        message={errors.username}
      >
        <Input
          value={formData.username}
          onValueChange={(value) => updateField('username', value)}
          placeholder="Enter username"
        />
      </FormField>

      <FormField 
        name="email" 
        label="Email" 
        invalid={!!errors.email}
        message={errors.email}
      >
        <Input
          value={formData.email}
          onValueChange={(value) => updateField('email', value)}
          placeholder="Enter email"
        />
      </FormField>

      {validating && (
        <div className="validation-indicator">
          <Spinner size="Small" />
          <Text type="BodySmall">Validating...</Text>
        </div>
      )}
    </Form>
  );
}
```

### Auto-save Functionality
```tsx
function AutoSaveExample() {
  const [document, setDocument] = useState({
    title: '',
    content: '',
    lastSaved: null
  });
  const [saveStatus, setSaveStatus] = useState('saved'); // 'saving', 'saved', 'error'

  const saveDocument = async (docData: typeof document) => {
    setSaveStatus('saving');
    try {
      const response = await fetch('/api/documents/save', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(docData)
      });
      
      if (response.ok) {
        setSaveStatus('saved');
        setDocument(prev => ({ ...prev, lastSaved: new Date() }));
      } else {
        setSaveStatus('error');
      }
    } catch (error) {
      setSaveStatus('error');
    }
  };

  const { debouncedCallback: debouncedSave, cancel: cancelSave } = useDebounce(
    saveDocument, 
    2000
  );

  const updateDocument = (updates: Partial<typeof document>) => {
    const newDoc = { ...document, ...updates };
    setDocument(newDoc);
    debouncedSave(newDoc);
  };

  const forceSave = () => {
    cancelSave(); // Cancel pending auto-save
    saveDocument(document); // Save immediately
  };

  return (
    <div className="document-editor">
      <div className="editor-header">
        <Input
          value={document.title}
          onValueChange={(title) => updateDocument({ title })}
          placeholder="Document title..."
          className="title-input"
        />
        
        <div className="save-status">
          {saveStatus === 'saving' && (
            <>
              <Spinner size="Small" />
              <Text type="BodySmall">Saving...</Text>
            </>
          )}
          {saveStatus === 'saved' && (
            <>
              <Icon icon="Check" size="Small" />
              <Text type="BodySmall">
                Saved {document.lastSaved ? formatTime(document.lastSaved) : ''}
              </Text>
            </>
          )}
          {saveStatus === 'error' && (
            <>
              <Icon icon="Error" size="Small" />
              <Text type="BodySmall">Save failed</Text>
            </>
          )}
        </div>

        <Button size="Small" onClick={forceSave}>
          Save Now
        </Button>
      </div>

      <TextArea
        value={document.content}
        onValueChange={(content) => updateDocument({ content })}
        placeholder="Start writing..."
        rows={20}
        className="content-editor"
      />
    </div>
  );
}
```

### Window Resize Handler
```tsx
function ResponsiveLayoutExample() {
  const [windowSize, setWindowSize] = useState({
    width: window.innerWidth,
    height: window.innerHeight
  });
  const [layout, setLayout] = useState('desktop');

  const updateLayout = () => {
    const { width } = windowSize;
    if (width < 768) {
      setLayout('mobile');
    } else if (width < 1024) {
      setLayout('tablet');
    } else {
      setLayout('desktop');
    }
  };

  const handleResize = () => {
    setWindowSize({
      width: window.innerWidth,
      height: window.innerHeight
    });
  };

  const { debouncedCallback: debouncedResize } = useDebounce(handleResize, 100);

  useEffect(() => {
    window.addEventListener('resize', debouncedResize);
    return () => window.removeEventListener('resize', debouncedResize);
  }, [debouncedResize]);

  useEffect(() => {
    updateLayout();
  }, [windowSize]);

  return (
    <div className={`layout layout-${layout}`}>
      <div className="debug-info">
        <Text type="BodySmall">
          {windowSize.width} x {windowSize.height} ({layout})
        </Text>
      </div>
      
      {layout === 'mobile' && <MobileLayout />}
      {layout === 'tablet' && <TabletLayout />}
      {layout === 'desktop' && <DesktopLayout />}
    </div>
  );
}
```

### API Rate Limiting
```tsx
function RateLimitedAPIExample() {
  const [suggestions, setSuggestions] = useState([]);
  const [loading, setLoading] = useState(false);
  const [requestCount, setRequestCount] = useState(0);

  const fetchSuggestions = async (query: string) => {
    if (!query.trim()) {
      setSuggestions([]);
      return;
    }

    setLoading(true);
    setRequestCount(prev => prev + 1);
    
    try {
      const response = await fetch(`/api/suggestions?q=${encodeURIComponent(query)}`);
      const data = await response.json();
      setSuggestions(data.suggestions);
    } catch (error) {
      console.error('Failed to fetch suggestions:', error);
    } finally {
      setLoading(false);
    }
  };

  // Debounce API calls to respect rate limits
  const { debouncedCallback: debouncedFetch } = useDebounce(fetchSuggestions, 500);

  return (
    <div>
      <div className="api-stats">
        <Text type="BodySmall">
          API Requests: {requestCount}
        </Text>
      </div>
      
      <Input
        onValueChange={debouncedFetch}
        placeholder="Type to get suggestions..."
      />
      
      {loading && <Spinner size="Small" />}
      
      <div className="suggestions">
        {suggestions.map((suggestion, index) => (
          <div key={index} className="suggestion-item">
            {suggestion}
          </div>
        ))}
      </div>
    </div>
  );
}
```

### Bulk Operations with Progress
```tsx
function BulkOperationExample() {
  const [selectedItems, setSelectedItems] = useState([]);
  const [processing, setProcessing] = useState(false);
  const [progress, setProgress] = useState(0);

  const processItems = async (items: string[]) => {
    if (items.length === 0) return;
    
    setProcessing(true);
    setProgress(0);
    
    for (let i = 0; i < items.length; i++) {
      try {
        await fetch(`/api/process/${items[i]}`, { method: 'POST' });
        setProgress(((i + 1) / items.length) * 100);
      } catch (error) {
        console.error(`Failed to process item ${items[i]}:`, error);
      }
    }
    
    setProcessing(false);
    setSelectedItems([]);
  };

  // Debounce bulk operations to prevent accidental multiple executions
  const { debouncedCallback: debouncedProcess } = useDebounce(processItems, 1000);

  const handleBulkAction = () => {
    if (selectedItems.length > 0) {
      debouncedProcess([...selectedItems]);
    }
  };

  return (
    <div>
      <div className="bulk-actions">
        <Text type="BodyMedium">
          {selectedItems.length} items selected
        </Text>
        <Button 
          onClick={handleBulkAction}
          disabled={selectedItems.length === 0 || processing}
        >
          Process Selected
        </Button>
      </div>

      {processing && (
        <div className="progress-indicator">
          <ProgressBar value={progress} max={100} />
          <Text type="BodySmall">
            Processing... {Math.round(progress)}%
          </Text>
        </div>
      )}

      {/* Item list with selection */}
      <div className="item-list">
        {items.map(item => (
          <CheckboxItem
            key={item.id}
            checked={selectedItems.includes(item.id)}
            onCheckedChange={(checked) => {
              if (checked) {
                setSelectedItems(prev => [...prev, item.id]);
              } else {
                setSelectedItems(prev => prev.filter(id => id !== item.id));
              }
            }}
          >
            {item.name}
          </CheckboxItem>
        ))}
      </div>
    </div>
  );
}
```

### Smart Notifications
```tsx
function SmartNotificationExample() {
  const [notifications, setNotifications] = useState([]);
  const [pendingNotification, setPendingNotification] = useState(null);

  const showNotification = (message: string, type: 'info' | 'success' | 'error' = 'info') => {
    const notification = {
      id: Date.now(),
      message,
      type,
      timestamp: new Date()
    };
    
    setNotifications(prev => [...prev, notification]);
    
    // Auto-remove after 5 seconds
    setTimeout(() => {
      setNotifications(prev => prev.filter(n => n.id !== notification.id));
    }, 5000);
  };

  // Debounce similar notifications to prevent spam
  const { debouncedCallback: debouncedNotify } = useDebounce(showNotification, 1000);

  const handleAction = (actionType: string) => {
    switch (actionType) {
      case 'save':
        debouncedNotify('Document saved successfully', 'success');
        break;
      case 'error':
        debouncedNotify('An error occurred', 'error');
        break;
      case 'info':
        debouncedNotify('Information updated', 'info');
        break;
    }
  };

  return (
    <div>
      <div className="action-buttons">
        <Button onClick={() => handleAction('save')}>
          Save (will be debounced)
        </Button>
        <Button onClick={() => handleAction('error')}>
          Trigger Error
        </Button>
        <Button onClick={() => handleAction('info')}>
          Show Info
        </Button>
      </div>

      <div className="notifications">
        {notifications.map(notification => (
          <div 
            key={notification.id} 
            className={`notification notification-${notification.type}`}
          >
            <Text type="BodyMedium">{notification.message}</Text>
            <Text type="BodySmall">
              {formatTime(notification.timestamp)}
            </Text>
          </div>
        ))}
      </div>
    </div>
  );
}
```

## Performance Benefits

1. **Reduces Function Calls**: Prevents excessive execution of expensive operations
2. **API Rate Limiting**: Helps comply with API rate limits and reduces server load
3. **UI Responsiveness**: Prevents UI blocking from frequent updates
4. **Memory Efficiency**: Automatic cleanup prevents memory leaks
5. **Bandwidth Optimization**: Reduces unnecessary network requests

## Best Practices

1. **Choose Appropriate Delays**: 
   - Search inputs: 300-500ms
   - Form validation: 500-1000ms
   - Auto-save: 1000-3000ms
   - Resize handlers: 100-250ms

2. **Stable Callback References**: Use `useCallback` for callback functions to prevent unnecessary re-debouncing

3. **Cleanup on Unmount**: The hook automatically handles cleanup, but you can manually cancel if needed

4. **Error Handling**: Always wrap debounced API calls in try-catch blocks

## Common Patterns

### Search Input
```tsx
const { debouncedCallback: debouncedSearch } = useDebounce(searchFunction, 300);
```

### Form Validation
```tsx
const { debouncedCallback: debouncedValidate } = useDebounce(validateForm, 500);
```

### Auto-save
```tsx
const { debouncedCallback: debouncedSave, cancel: cancelSave } = useDebounce(saveDocument, 2000);
```

## TypeScript Support

The hook is fully typed and preserves the original function signature:

```tsx
const myFunction = (a: string, b: number) => console.log(a, b);
const { debouncedCallback } = useDebounce(myFunction, 500);

// TypeScript knows the parameters are (a: string, b: number)
debouncedCallback('hello', 42); // ✅ Correctly typed
debouncedCallback(42, 'hello'); // ❌ TypeScript error
```

## Related Hooks

- **[React.useCallback](https://reactjs.org/docs/hooks-reference.html#usecallback)** - Memoization for functions
- **[React.useEffect](https://reactjs.org/docs/hooks-reference.html#useeffect)** - Side effects management
- **[Search Component](../molecules/Search.md)** - Uses useDebounce internally