Custom Hooks - Reusable Logic
What are Custom Hooks?
Custom hooks let you extract component logic into reusable functions. They start with "use" and can call other hooks.
Basic Custom Hook
function useToggle(initialValue: boolean = false) {
const [value, setValue] = useState(initialValue);
const toggle = useCallback(() => {
setValue(v => !v);
}, []);
return [value, toggle] as const;
}
// Usage
function Modal() {
const [isOpen, toggleOpen] = useToggle(false);
return (
<>
<button onClick={toggleOpen}>Open Modal</button>
{isOpen && <ModalContent onClose={toggleOpen} />}
</>
);
}
useLocalStorage Hook
function useLocalStorage\<T\>(key: string, initialValue: T) {
const [storedValue, setStoredValue] = useState\<T\>(() => {
try {
const item = window.localStorage.getItem(key);
return item ? JSON.parse(item) : initialValue;
} catch (error) {
console.error(error);
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;
}
useDebounce Hook
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 Search() {
const [searchTerm, setSearchTerm] = useState('');
const debouncedSearchTerm = useDebounce(searchTerm, 500);
useEffect(() => {
if (debouncedSearchTerm) {
searchAPI(debouncedSearchTerm).then(setResults);
}
}, [debouncedSearchTerm]);
return (
<input value={searchTerm} onChange={e => setSearchTerm(e.target.value)} />
);
}
useFetch Hook
interface FetchState\<T\> {
data: T | null;
loading: boolean;
error: Error | null;
}
function useFetch\<T\>(url: string) {
const [state, setState] = useState<FetchState\<T\>>({
data: null,
loading: true,
error: null,
});
useEffect(() => {
let cancelled = false;
async function fetchData() {
setState({ data: null, loading: true, error: null });
try {
const response = await fetch(url);
const data = await response.json();
if (!cancelled) {
setState({ data, loading: false, error: null });
}
} catch (error) {
if (!cancelled) {
setState({ data: null, loading: false, error: error as Error });
}
}
}
fetchData();
return () => {
cancelled = true;
};
}, [url]);
return state;
}
// Usage
function UserProfile({ userId }: { userId: string }) {
const { data: user, loading, error } = useFetch<User>(`/api/users/${userId}`);
if (loading) return <Spinner />;
if (error) return <Error message={error.message} />;
return <UserCard user={user!} />;
}
useOnClickOutside Hook
function useOnClickOutside<T extends HTMLElement = HTMLElement>(
ref: RefObject\<T\>,
handler: (event: MouseEvent | TouchEvent) => void
) {
useEffect(() => {
const listener = (event: MouseEvent | TouchEvent) => {
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 ref = useRef<HTMLDivElement>(null);
useOnClickOutside(ref, () => setIsOpen(false));
return (
<div ref={ref}>
<button onClick={() => setIsOpen(!isOpen)}>Toggle</button>
{isOpen && <DropdownMenu />}
</div>
);
}
Best Practices
- Start with "use" - Convention for hooks
- Extract reusable logic - Don't repeat yourself
- Compose hooks - Use hooks inside hooks
- Return consistent types - Use
as constfor tuples - Handle cleanup - Return cleanup functions
- Type generics - Make hooks flexible
Practice Exercises
- Create
useAsyncfor async operations - Build
useMediaQueryfor responsive design - Implement
useFormfor form management - Create
useWebSocketfor real-time data
Next Steps
- Practice building more custom hooks
- Explore advanced hook patterns