react-hooks
React Custom Hooks
Reusable hook patterns for common UI scenarios.
Instructions
1. useDebounce
function useDebounce<T>(value: T, delay: number): T {
const [debouncedValue, setDebouncedValue] = useState(value);
useEffect(() => {
const timer = setTimeout(() => setDebouncedValue(value), delay);
return () => clearTimeout(timer);
}, [value, delay]);
return debouncedValue;
}
// Usage
function SearchInput() {
const [query, setQuery] = useState('');
const debouncedQuery = useDebounce(query, 300);
useEffect(() => {
if (debouncedQuery) {
searchAPI(debouncedQuery);
}
}, [debouncedQuery]);
}
2. useLocalStorage
function useLocalStorage<T>(key: string, initialValue: T) {
const [storedValue, setStoredValue] = useState<T>(() => {
if (typeof window === 'undefined') return initialValue;
try {
const item = window.localStorage.getItem(key);
return item ? JSON.parse(item) : initialValue;
} catch {
return initialValue;
}
});
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);
}
};
return [storedValue, setValue] as const;
}
// Usage
const [theme, setTheme] = useLocalStorage('theme', 'dark');
3. useMediaQuery
function useMediaQuery(query: string): boolean {
const [matches, setMatches] = useState(false);
useEffect(() => {
const media = window.matchMedia(query);
setMatches(media.matches);
const listener = (e: MediaQueryListEvent) => setMatches(e.matches);
media.addEventListener('change', listener);
return () => media.removeEventListener('change', listener);
}, [query]);
return matches;
}
// Usage
function Component() {
const isMobile = useMediaQuery('(max-width: 768px)');
const isDark = useMediaQuery('(prefers-color-scheme: dark)');
}
4. useClickOutside
function useClickOutside<T extends HTMLElement>(
handler: () => void
): RefObject<T> {
const ref = useRef<T>(null);
useEffect(() => {
const listener = (event: MouseEvent | TouchEvent) => {
if (!ref.current || ref.current.contains(event.target as Node)) {
return;
}
handler();
};
document.addEventListener('mousedown', listener);
document.addEventListener('touchstart', listener);
return () => {
document.removeEventListener('mousedown', listener);
document.removeEventListener('touchstart', listener);
};
}, [handler]);
return ref;
}
// Usage
function Dropdown() {
const [isOpen, setIsOpen] = useState(false);
const ref = useClickOutside<HTMLDivElement>(() => setIsOpen(false));
return <div ref={ref}>{isOpen && <Menu />}</div>;
}
5. useToggle
function useToggle(initialValue = false) {
const [value, setValue] = useState(initialValue);
const toggle = useCallback(() => setValue(v => !v), []);
const setTrue = useCallback(() => setValue(true), []);
const setFalse = useCallback(() => setValue(false), []);
return { value, toggle, setTrue, setFalse };
}
// Usage
const { value: isOpen, toggle, setFalse: close } = useToggle();
6. usePrevious
function usePrevious<T>(value: T): T | undefined {
const ref = useRef<T>();
useEffect(() => {
ref.current = value;
}, [value]);
return ref.current;
}
// Usage
function Counter({ count }: { count: number }) {
const prevCount = usePrevious(count);
// prevCount is the previous value of count
}
7. useAsync
interface AsyncState<T> {
data: T | null;
loading: boolean;
error: Error | null;
}
function useAsync<T>(asyncFn: () => Promise<T>, deps: unknown[] = []) {
const [state, setState] = useState<AsyncState<T>>({
data: null,
loading: true,
error: null,
});
useEffect(() => {
setState(s => ({ ...s, loading: true }));
asyncFn()
.then(data => setState({ data, loading: false, error: null }))
.catch(error => setState({ data: null, loading: false, error }));
}, deps);
return state;
}
// Usage
const { data, loading, error } = useAsync(() => fetchUser(id), [id]);
8. useCopyToClipboard
function useCopyToClipboard() {
const [copiedText, setCopiedText] = useState<string | null>(null);
const copy = async (text: string) => {
try {
await navigator.clipboard.writeText(text);
setCopiedText(text);
return true;
} catch {
setCopiedText(null);
return false;
}
};
return { copiedText, copy };
}
9. useEventListener
function useEventListener<K extends keyof WindowEventMap>(
eventName: K,
handler: (event: WindowEventMap[K]) => void,
element: Window | HTMLElement = window
) {
const savedHandler = useRef(handler);
useEffect(() => {
savedHandler.current = handler;
}, [handler]);
useEffect(() => {
const listener = (event: Event) => savedHandler.current(event as WindowEventMap[K]);
element.addEventListener(eventName, listener);
return () => element.removeEventListener(eventName, listener);
}, [eventName, element]);
}
// Usage
useEventListener('scroll', () => console.log('scrolled'));
useEventListener('keydown', (e) => {
if (e.key === 'Escape') closeModal();
});
10. useIntersectionObserver
function useIntersectionObserver(
ref: RefObject<Element>,
options?: IntersectionObserverInit
): boolean {
const [isIntersecting, setIsIntersecting] = useState(false);
useEffect(() => {
if (!ref.current) return;
const observer = new IntersectionObserver(([entry]) => {
setIsIntersecting(entry.isIntersecting);
}, options);
observer.observe(ref.current);
return () => observer.disconnect();
}, [ref, options]);
return isIntersecting;
}
// Usage - Lazy loading
function LazyImage({ src }: { src: string }) {
const ref = useRef<HTMLDivElement>(null);
const isVisible = useIntersectionObserver(ref, { threshold: 0.1 });
return (
<div ref={ref}>
{isVisible && <img src={src} />}
</div>
);
}
Best Practices
| Do | Don't |
|---|---|
✅ Prefix with use |
❌ Name without use |
| ✅ Return stable references | ❌ Return new objects each render |
| ✅ Handle cleanup | ❌ Forget cleanup in useEffect |
| ✅ Use TypeScript generics | ❌ Use any types |
References
More from alicoder001/agent-skills
typescript
TypeScript strict mode patterns, naming conventions, and type safety rules. Use when writing TypeScript code, defining types, or reviewing TypeScript projects. Includes generics, utility types, and best practices.
35collaboration
Multi-agent communication, task delegation, and coordination patterns. Use when working with multiple agents or complex collaborative workflows.
27solid
SOLID, DRY, KISS, and clean code principles for TypeScript applications. Use when designing scalable architecture, writing maintainable code, or reviewing code quality.
25security
Security best practices for web applications. Use when handling user input, authentication, or sensitive data. Covers XSS, SQL injection, CSRF, environment variables, and secure coding patterns.
22memory
Working memory management, context prioritization, and knowledge retention patterns for AI agents. Use when you need to maintain relevant context and avoid information loss during long tasks.
22react-vite
React + Vite SPA development patterns including project setup, configuration, and optimization. Use for building single-page applications with Vite bundler.
22