Skip to main content

Custom Hooks Utiles

Collection de hooks React réutilisables que j'utilise fréquemment dans mes projets.

useDebounce

Hook pour debouncer une valeur (utile pour les inputs de recherche).

import { useState, useEffect } from 'react';

export function useDebounce<T>(value: T, delay: number = 500): T {
const [debouncedValue, setDebouncedValue] = useState<T>(value);

useEffect(() => {
const handler = setTimeout(() => {
setDebouncedValue(value);
}, delay);

return () => {
clearTimeout(handler);
};
}, [value, delay]);

return debouncedValue;
}

Utilisation :

function SearchComponent() {
const [searchTerm, setSearchTerm] = useState('');
const debouncedSearchTerm = useDebounce(searchTerm, 300);

useEffect(() => {
if (debouncedSearchTerm) {
// Faire l'appel API ici
searchAPI(debouncedSearchTerm);
}
}, [debouncedSearchTerm]);

return (
<input
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
placeholder="Rechercher..."
/>
);
}

useLocalStorage

Hook pour synchroniser un état avec localStorage.

import { useState, useEffect } from 'react';

export function useLocalStorage<T>(
key: string,
initialValue: T
): [T, (value: T | ((val: T) => T)) => void] {
// Initialiser avec la valeur du localStorage ou la valeur par défaut
const [storedValue, setStoredValue] = useState<T>(() => {
try {
const item = window.localStorage.getItem(key);
return item ? JSON.parse(item) : initialValue;
} catch (error) {
console.error(`Error loading localStorage key "${key}":`, error);
return initialValue;
}
});

// Wrapper pour sauvegarder dans localStorage
const setValue = (value: T | ((val: T) => T)) => {
try {
const valueToStore = value instanceof Function ? value(storedValue) : value;
setStoredValue(valueToStore);
window.localStorage.setItem(key, JSON.stringify(valueToStore));
} catch (error) {
console.error(`Error saving localStorage key "${key}":`, error);
}
};

return [storedValue, setValue];
}

Utilisation :

function ThemeToggle() {
const [theme, setTheme] = useLocalStorage('theme', 'light');

return (
<button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
Current theme: {theme}
</button>
);
}

useOnClickOutside

Hook pour détecter les clics en dehors d'un élément (utile pour les dropdowns, modals).

import { useEffect, RefObject } from 'react';

export function useOnClickOutside<T extends HTMLElement = HTMLElement>(
ref: RefObject<T>,
handler: (event: MouseEvent | TouchEvent) => void
) {
useEffect(() => {
const listener = (event: MouseEvent | TouchEvent) => {
const el = ref?.current;

// Ne rien faire si on clique sur l'élément ou ses enfants
if (!el || el.contains(event.target as Node)) {
return;
}

handler(event);
};

document.addEventListener('mousedown', listener);
document.addEventListener('touchstart', listener);

return () => {
document.removeEventListener('mousedown', listener);
document.removeEventListener('touchstart', listener);
};
}, [ref, handler]);
}

Utilisation :

function Dropdown() {
const [isOpen, setIsOpen] = useState(false);
const dropdownRef = useRef<HTMLDivElement>(null);

useOnClickOutside(dropdownRef, () => setIsOpen(false));

return (
<div ref={dropdownRef}>
<button onClick={() => setIsOpen(!isOpen)}>Toggle</button>
{isOpen && (
<div className="dropdown-menu">
<p>Contenu du dropdown</p>
</div>
)}
</div>
);
}

useMediaQuery

Hook pour réagir aux media queries CSS.

import { useState, useEffect } from 'react';

export function useMediaQuery(query: string): boolean {
const [matches, setMatches] = useState(false);

useEffect(() => {
const media = window.matchMedia(query);

// Set initial value
setMatches(media.matches);

// Create listener
const listener = (event: MediaQueryListEvent) => {
setMatches(event.matches);
};

// Add listener
media.addEventListener('change', listener);

return () => media.removeEventListener('change', listener);
}, [query]);

return matches;
}

Utilisation :

function ResponsiveComponent() {
const isMobile = useMediaQuery('(max-width: 768px)');
const isDarkMode = useMediaQuery('(prefers-color-scheme: dark)');

return (
<div>
{isMobile ? <MobileView /> : <DesktopView />}
{isDarkMode && <p>Mode sombre détecté</p>}
</div>
);
}

usePrevious

Hook pour garder la valeur précédente d'une variable.

import { useRef, useEffect } from 'react';

export function usePrevious<T>(value: T): T | undefined {
const ref = useRef<T>();

useEffect(() => {
ref.current = value;
}, [value]);

return ref.current;
}

Utilisation :

function Counter() {
const [count, setCount] = useState(0);
const prevCount = usePrevious(count);

return (
<div>
<p>Current: {count}</p>
<p>Previous: {prevCount}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
}

useAsync

Hook pour gérer les états de chargement asynchrone.

import { useState, useEffect, useCallback } from 'react';

interface AsyncState<T> {
data: T | null;
loading: boolean;
error: Error | null;
}

export function useAsync<T>(
asyncFunction: () => Promise<T>,
immediate = true
) {
const [state, setState] = useState<AsyncState<T>>({
data: null,
loading: immediate,
error: null,
});

const execute = useCallback(async () => {
setState({ data: null, loading: true, error: null });

try {
const data = await asyncFunction();
setState({ data, loading: false, error: null });
return data;
} catch (error) {
setState({ data: null, loading: false, error: error as Error });
throw error;
}
}, [asyncFunction]);

useEffect(() => {
if (immediate) {
execute();
}
}, [execute, immediate]);

return { ...state, execute };
}

Utilisation :

function UserProfile({ userId }: { userId: string }) {
const { data, loading, error } = useAsync(
() => fetchUser(userId),
true // Exécuter immédiatement
);

if (loading) return <Spinner />;
if (error) return <Error message={error.message} />;
if (!data) return null;

return <div>{data.name}</div>;
}

Bonnes pratiques

  • Toujours typer les hooks avec TypeScript
  • Mémoriser les callbacks avec useCallback pour éviter les re-renders
  • Nettoyer les effets (timeouts, event listeners, etc.)
  • Gérer les erreurs proprement dans les hooks async
  • Documenter les hooks complexes avec des exemples

Ressources