Claude-skill-registry custom-hooks
Implement custom React hooks for reusable logic including state management, side effects, and data fetching. Use when extracting component logic into reusable hooks.
install
source · Clone the upstream repo
git clone https://github.com/majiayu000/claude-skill-registry
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/custom-hooks" ~/.claude/skills/majiayu000-claude-skill-registry-custom-hooks && rm -rf "$T"
manifest:
skills/data/custom-hooks/SKILL.mdsource content
You are a React custom hooks expert. You help create reusable, well-typed custom hooks that encapsulate common patterns and logic.
Custom Hook Patterns
1. useLocalStorage - Persistent State
// hooks/useLocalStorage.ts import { useState, useEffect } from 'react'; export function useLocalStorage<T>( key: string, initialValue: T ): [T, (value: T | ((val: T) => T)) => void] { // Get initial value from localStorage or use provided initial value 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 (error) { console.error(`Error loading localStorage key "${key}":`, error); return initialValue; } }); // Update localStorage when value changes const setValue = (value: T | ((val: T) => T)) => { try { const valueToStore = value instanceof Function ? value(storedValue) : value; setStoredValue(valueToStore); if (typeof window !== 'undefined') { window.localStorage.setItem(key, JSON.stringify(valueToStore)); } } catch (error) { console.error(`Error setting localStorage key "${key}":`, error); } }; return [storedValue, setValue]; } // Usage function ThemeToggle() { const [theme, setTheme] = useLocalStorage('theme', 'light'); return ( <button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}> Current theme: {theme} </button> ); }
2. useDebounce - Delay Value Updates
// hooks/useDebounce.ts import { useState, useEffect } from 'react'; export function useDebounce<T>(value: T, delay: number): T { const [debouncedValue, setDebouncedValue] = useState<T>(value); useEffect(() => { const handler = setTimeout(() => { setDebouncedValue(value); }, delay); return () => { clearTimeout(handler); }; }, [value, delay]); return debouncedValue; } // Usage function SearchBox() { const [searchTerm, setSearchTerm] = useState(''); const debouncedSearchTerm = useDebounce(searchTerm, 500); useEffect(() => { if (debouncedSearchTerm) { // API call only happens 500ms after user stops typing api.search(debouncedSearchTerm); } }, [debouncedSearchTerm]); return <input value={searchTerm} onChange={(e) => setSearchTerm(e.target.value)} />; }
3. useMediaQuery - Responsive Breakpoints
// hooks/useMediaQuery.ts 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 event listener const listener = (e: MediaQueryListEvent) => setMatches(e.matches); // Add listener media.addEventListener('change', listener); // Cleanup return () => media.removeEventListener('change', listener); }, [query]); return matches; } // Usage function ResponsiveComponent() { const isMobile = useMediaQuery('(max-width: 768px)'); const isTablet = useMediaQuery('(min-width: 769px) and (max-width: 1024px)'); const isDesktop = useMediaQuery('(min-width: 1025px)'); return ( <div> {isMobile && <MobileView />} {isTablet && <TabletView />} {isDesktop && <DesktopView />} </div> ); }
4. useOnClickOutside - Detect Outside Clicks
// hooks/useOnClickOutside.ts import { useEffect, RefObject } from 'react'; export function useOnClickOutside<T extends HTMLElement>( ref: RefObject<T>, handler: (event: MouseEvent | TouchEvent) => void ): void { useEffect(() => { const listener = (event: MouseEvent | TouchEvent) => { // Do nothing if clicking ref's element or descendent elements if (!ref.current || ref.current.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]); } // Usage 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>Dropdown content</div>} </div> ); }
5. useAsync - Async Operation State
// hooks/useAsync.ts import { useState, useEffect, useCallback } from 'react'; interface AsyncState<T> { data: T | null; error: Error | null; isLoading: boolean; } export function useAsync<T>( asyncFunction: () => Promise<T>, immediate = true ): AsyncState<T> & { execute: () => Promise<void> } { const [state, setState] = useState<AsyncState<T>>({ data: null, error: null, isLoading: immediate, }); const execute = useCallback(async () => { setState({ data: null, error: null, isLoading: true }); try { const data = await asyncFunction(); setState({ data, error: null, isLoading: false }); } catch (error) { setState({ data: null, error: error as Error, isLoading: false }); } }, [asyncFunction]); useEffect(() => { if (immediate) { execute(); } }, [execute, immediate]); return { ...state, execute }; } // Usage function UserProfile({ userId }: { userId: string }) { const { data: user, isLoading, error } = useAsync( () => api.getUser(userId), true ); if (isLoading) return <div>Loading...</div>; if (error) return <div>Error: {error.message}</div>; if (!user) return null; return <div>{user.name}</div>; }
6. usePrevious - Track Previous Value
// hooks/usePrevious.ts 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; } // Usage 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> ); }
7. useToggle - Boolean State Toggle
// hooks/useToggle.ts import { useState, useCallback } from 'react'; export function useToggle( initialValue = false ): [boolean, () => void, (value: boolean) => void] { const [value, setValue] = useState(initialValue); const toggle = useCallback(() => { setValue((v) => !v); }, []); return [value, toggle, setValue]; } // Usage function Modal() { const [isOpen, toggleOpen, setIsOpen] = useToggle(false); return ( <> <button onClick={toggleOpen}>Toggle Modal</button> {isOpen && ( <div> <p>Modal Content</p> <button onClick={() => setIsOpen(false)}>Close</button> </div> )} </> ); }
8. useWindowSize - Track Window Dimensions
// hooks/useWindowSize.ts import { useState, useEffect } from 'react'; interface WindowSize { width: number; height: number; } export function useWindowSize(): WindowSize { const [windowSize, setWindowSize] = useState<WindowSize>({ width: window.innerWidth, height: window.innerHeight, }); useEffect(() => { const handleResize = () => { setWindowSize({ width: window.innerWidth, height: window.innerHeight, }); }; window.addEventListener('resize', handleResize); return () => window.removeEventListener('resize', handleResize); }, []); return windowSize; } // Usage function ResponsiveComponent() { const { width, height } = useWindowSize(); return ( <div> Window size: {width} x {height} </div> ); }
Best Practices for Custom Hooks
- Naming: Always start with "use" (React requirement)
- Return Values:
- Single value: Return directly
- Multiple related values: Return as object
- Pair of values (state/setter): Return as tuple
- TypeScript: Always add proper type definitions
- Cleanup: Return cleanup functions from useEffect
- Dependencies: Carefully manage dependency arrays
- Memoization: Use useCallback for returned functions
- Documentation: Add JSDoc comments for complex hooks
When to Create Custom Hooks
Create custom hooks when:
- Logic is reused across multiple components
- Complex state management logic needs encapsulation
- Side effects need to be abstracted
- Component logic becomes too complex
- You find yourself copying code between components
This skill helps you create reusable, well-designed custom hooks following React best practices.