| name | react-hooks-complete |
| description | Complete React hooks reference system. PROACTIVELY activate for: (1) useState and useReducer patterns, (2) useEffect and lifecycle, (3) useRef for DOM and values, (4) useContext for shared state, (5) useMemo and useCallback optimization, (6) React 19 hooks (useTransition, useDeferredValue, useActionState, useOptimistic), (7) Custom hook development, (8) Hook rules and best practices. Provides: Hook syntax, effect cleanup patterns, performance optimization, custom hook patterns. Ensures correct hook usage following React best practices. |
Quick Reference
| Hook | Purpose | Example |
|---|
useState | Local state | const [count, setCount] = useState(0) |
useReducer | Complex state | const [state, dispatch] = useReducer(reducer, init) |
useEffect | Side effects | useEffect(() => { ... }, [deps]) |
useRef | Mutable ref / DOM | const ref = useRef<HTMLInputElement>(null) |
useContext | Consume context | const value = useContext(MyContext) |
useMemo | Memoize value | const computed = useMemo(() => calc(), [deps]) |
useCallback | Stable function | const fn = useCallback(() => {}, [deps]) |
| React 19 Hook | Purpose |
|---|
useTransition | Non-blocking updates |
useDeferredValue | Defer expensive renders |
useActionState | Server Action state |
useOptimistic | Optimistic UI updates |
useFormStatus | Form pending state |
When to Use This Skill
Use for React hooks implementation:
- Choosing the right hook for your use case
- Managing component state with useState/useReducer
- Handling side effects and cleanup with useEffect
- Building custom reusable hooks
- Optimizing with useMemo and useCallback
- Using React 19 concurrent features
For state management libraries: see react-state-management
React Hooks Complete Guide
State Hooks
useState
import { useState } from 'react';
const [count, setCount] = useState(0);
const [state, setState] = useState(() => {
const initialValue = expensiveComputation();
return initialValue;
});
const [user, setUser] = useState<User | null>(null);
setUser((prev) => prev ? { ...prev, name: 'New Name' } : null);
const [items, setItems] = useState<Item[]>([]);
setItems((prev) => [...prev, newItem]);
setItems((prev) => prev.filter((item) => item.id !== id));
setItems((prev) =>
prev.map((item) => (item.id === id ? { ...item, ...updates } : item))
);
useReducer
import { useReducer } from 'react';
interface State {
count: number;
error: string | null;
loading: boolean;
}
type Action =
| { type: 'increment' }
| { type: 'decrement' }
| { type: 'reset'; payload: number }
| { type: 'setError'; payload: string };
function reducer(state: State, action: Action): State {
switch (action.type) {
case 'increment':
return { ...state, count: state.count + 1 };
case 'decrement':
return { ...state, count: state.count - 1 };
case 'reset':
return { ...state, count: action. };
:
{ ...state, : action. };
:
state;
}
}
() {
[state, dispatch] = (reducer, {
: ,
: ,
: ,
});
(
);
}
useReducer with Immer
import { useReducer } from 'react';
import { produce } from 'immer';
interface Todo {
id: number;
text: string;
completed: boolean;
}
type Action =
| { type: 'add'; text: string }
| { type: 'toggle'; id: number }
| { type: 'delete'; id: number };
function todosReducer(state: Todo[], action: Action): Todo[] {
return produce(state, (draft) => {
switch (action.type) {
case 'add':
draft.push({
id: Date.now(),
text: action.text,
completed: false,
});
break;
case 'toggle':
todo = draft.( t. === action.);
(todo) todo. = !todo.;
;
:
index = draft.( t. === action.);
(index !== -) draft.(index, );
;
}
});
}
Effect Hooks
useEffect
import { useEffect, useState } from 'react';
function UserProfile({ userId }: { userId: string }) {
const [user, setUser] = useState<User | null>(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
let cancelled = false;
async function fetchUser() {
setLoading(true);
try {
const response = await fetch(`/api/users/${userId}`);
const data = await response.json();
if (!cancelled) {
setUser(data);
}
} catch (error) {
if (!cancelled) {
console.error('Failed to fetch user:', error);
}
} finally {
if (!cancelled) {
setLoading(false);
}
}
}
fetchUser();
return () => {
cancelled = ;
};
}, [userId]);
(loading) ;
(!user) ;
;
}
useLayoutEffect
import { useLayoutEffect, useRef, useState } from 'react';
function Tooltip({ text, children }: { text: string; children: ReactNode }) {
const [tooltipHeight, setTooltipHeight] = useState(0);
const tooltipRef = useRef<HTMLDivElement>(null);
useLayoutEffect(() => {
if (tooltipRef.current) {
setTooltipHeight(tooltipRef.current.getBoundingClientRect().height);
}
}, [text]);
return (
<div className="tooltip-container">
{children}
<div
ref={tooltipRef}
className="tooltip"
style={{ top: -tooltipHeight - 10 }}
>
{text}
</div>
</div>
);
}
useInsertionEffect (for CSS-in-JS libraries)
import { useInsertionEffect } from 'react';
function useCSS(rule: string) {
useInsertionEffect(() => {
const style = document.createElement('style');
style.textContent = rule;
document.head.appendChild(style);
return () => {
document.head.removeChild(style);
};
}, [rule]);
}
Context Hook
useContext
import { createContext, useContext, useState, ReactNode } from 'react';
interface AuthContextType {
user: User | null;
login: (email: string, password: string) => Promise<void>;
logout: () => void;
isAuthenticated: boolean;
}
const AuthContext = createContext<AuthContextType | null>(null);
export function AuthProvider({ children }: { children: ReactNode }) {
const [user, setUser] = useState<User | null>(null);
const login = async (email: string, password: string) => {
const response = await fetch('/api/login', {
method: 'POST',
: .({ email, password }),
});
userData = response.();
(userData);
};
= () => {
();
};
(
);
}
() {
context = ();
(!context) {
();
}
context;
}
() {
{ isAuthenticated, logout, user } = ();
(isAuthenticated) {
(
);
}
;
}
Ref Hooks
useRef
import { useRef, useEffect } from 'react';
function TextInput() {
const inputRef = useRef<HTMLInputElement>(null);
useEffect(() => {
inputRef.current?.focus();
}, []);
return <input ref={inputRef} type="text" />;
}
function Timer() {
const intervalRef = useRef<NodeJS.Timeout | null>(null);
const [count, setCount] = useState(0);
const startTimer = () => {
if (intervalRef.current) return;
intervalRef.current = setInterval(() => {
setCount((c) => c + 1);
}, 1000);
};
const stopTimer = () => {
if (intervalRef.current) {
(intervalRef.);
intervalRef. = ;
}
};
( {
{
(intervalRef.) {
(intervalRef.);
}
};
}, []);
(
);
}
useImperativeHandle
import { forwardRef, useImperativeHandle, useRef } from 'react';
interface VideoPlayerHandle {
play: () => void;
pause: () => void;
seek: (time: number) => void;
}
const VideoPlayer = forwardRef<VideoPlayerHandle, { src: string }>(
({ src }, ref) => {
const videoRef = useRef<HTMLVideoElement>(null);
useImperativeHandle(ref, () => ({
play() {
videoRef.current?.play();
},
pause() {
videoRef.current?.pause();
},
seek(time: number) {
if (videoRef.current) {
videoRef.current.currentTime = time;
}
},
}));
return <video ref={videoRef} src={src} />;
}
);
() {
playerRef = useRef<>();
(
);
}
Performance Hooks
useMemo
import { useMemo, useState } from 'react';
function ProductList({ products, filter }: Props) {
const filteredProducts = useMemo(() => {
console.log('Filtering products...');
return products.filter((product) => {
if (filter.category && product.category !== filter.category) {
return false;
}
if (filter.minPrice && product.price < filter.minPrice) {
return false;
}
if (filter.maxPrice && product.price > filter.maxPrice) {
return false;
}
return true;
});
}, [products, filter]);
const stats = useMemo(
() => ({
total: filteredProducts.length,
avgPrice:
filteredProducts.reduce((sum, p) => sum + p.price, ) /
filteredProducts.,
}),
[filteredProducts]
);
(
);
}
useCallback
import { useCallback, useState, memo } from 'react';
const TodoItem = memo(function TodoItem({
todo,
onToggle,
onDelete,
}: {
todo: Todo;
onToggle: (id: number) => void;
onDelete: (id: number) => void;
}) {
console.log('Rendering todo:', todo.id);
return (
<li>
<input
type="checkbox"
checked={todo.completed}
onChange={() => onToggle(todo.id)}
/>
<span>{todo.text}</span>
<button onClick={() => onDelete(todo.id)}>Delete</button>
</li>
);
});
function TodoList() {
const [todos, setTodos] = useState<Todo[]>([]);
const handleToggle = useCallback((id: number) => {
(
prev.(
todo. === id ? { ...todo, : !todo. } : todo
)
);
}, []);
handleDelete = ( {
( prev.( todo. !== id));
}, []);
(
);
}
React 19 Hooks
useTransition
import { useState, useTransition } from 'react';
function TabContainer() {
const [tab, setTab] = useState('about');
const [isPending, startTransition] = useTransition();
function selectTab(nextTab: string) {
startTransition(() => {
setTab(nextTab);
});
}
return (
<div>
<nav>
{['about', 'posts', 'contact'].map((t) => (
<button
key={t}
onClick={() => selectTab(t)}
className={tab === t ? 'active' : ''}
>
{t}
</button>
))}
</nav>
<div className={isPending ? 'pending' : ''}>
{tab === 'about' && <AboutTab />}
{tab === 'posts' && <PostsTab />}
{tab === 'contact' && <ContactTab />}
</div>
</div>
);
}
useDeferredValue
import { useState, useDeferredValue, memo } from 'react';
const SlowList = memo(function SlowList({ text }: { text: string }) {
const items = [];
for (let i = 0; i < 20000; i++) {
items.push(<li key={i}>{text}</li>);
}
return <ul>{items}</ul>;
});
function App() {
const [text, setText] = useState('');
const deferredText = useDeferredValue(text);
return (
<div>
<input
value={text}
onChange={(e) => setText(e.target.value)}
placeholder="Type here..."
/>
{/* Input stays responsive while list updates are deferred */}
<SlowList text={deferredText} />
</div>
);
}
useActionState
'use client';
import { useActionState } from 'react';
async function submitForm(
prevState: { message: string } | null,
formData: FormData
) {
'use server';
const email = formData.get('email') as string;
if (!email.includes('@')) {
return { message: 'Invalid email address' };
}
await subscribeToNewsletter(email);
return { message: 'Subscribed successfully!' };
}
function NewsletterForm() {
const [state, formAction, isPending] = useActionState(submitForm, null);
return (
<form action={formAction}>
<input type="email" name="email" placeholder="Enter email" required />
<button type="submit" =>
{isPending ? 'Subscribing...' : 'Subscribe'}
{state?.message && {state.message}}
);
}
useOptimistic
'use client';
import { useOptimistic, useState } from 'react';
import { likePost } from './actions';
function LikeButton({ postId, initialLikes }: Props) {
const [likes, setLikes] = useState(initialLikes);
const [optimisticLikes, addOptimisticLike] = useOptimistic(
likes,
(state, _) => state + 1
);
async function handleLike() {
addOptimisticLike(null);
const newLikes = await likePost(postId);
setLikes(newLikes);
}
return (
<button onClick={handleLike}>
Like ({optimisticLikes})
</button>
);
}
useFormStatus
'use client';
import { useFormStatus } from 'react-dom';
function SubmitButton() {
const { pending, data, method, action } = useFormStatus();
return (
<button type="submit" disabled={pending}>
{pending ? 'Submitting...' : 'Submit'}
</button>
);
}
function Form() {
return (
<form action={submitAction}>
<input type="text" name="name" />
<SubmitButton />
</form>
);
}
Custom Hooks
useLocalStorage
import { useState, useEffect } from 'react';
function useLocalStorage<T>(key: string, initialValue: T) {
const [value, setValue] = useState<T>(() => {
if (typeof window === 'undefined') return initialValue;
try {
const item = localStorage.getItem(key);
return item ? JSON.parse(item) : initialValue;
} catch {
return initialValue;
}
});
useEffect(() => {
try {
localStorage.setItem(key, JSON.stringify(value));
} catch (error) {
console.error('Failed to save to localStorage:', error);
}
}, [key, value]);
return [value, setValue] as const;
}
function App() {
const [theme, setTheme] = useLocalStorage('theme', 'light');
return <button = => setTheme(theme === 'light' ? 'dark' : 'light')}>Toggle;
}
useDebounce
import { useState, useEffect } from 'react';
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;
}
function SearchInput() {
const [query, setQuery] = useState('');
const debouncedQuery = useDebounce(query, 300);
useEffect(() => {
if (debouncedQuery) {
searchAPI(debouncedQuery);
}
}, [debouncedQuery]);
return <input value={query} onChange={(e) => setQuery(e.target.value)} />;
}
useFetch
import { useState, useEffect } from 'react';
interface FetchState<T> {
data: T | null;
loading: boolean;
error: Error | null;
}
function useFetch<T>(url: string): FetchState<T> {
const [state, setState] = useState<FetchState<T>>({
data: null,
loading: true,
error: null,
});
useEffect(() => {
let cancelled = false;
const fetchData = async () => {
setState({ data: null, loading: true, error: null });
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.();
(!cancelled) {
({ data, : , : });
}
} (error) {
(!cancelled) {
({
: ,
: ,
: error ? error : (),
});
}
}
};
();
{
cancelled = ;
};
}, [url]);
state;
}
useMediaQuery
import { useState, useEffect } from 'react';
function useMediaQuery(query: string): boolean {
const [matches, setMatches] = useState(false);
useEffect(() => {
const mediaQuery = window.matchMedia(query);
setMatches(mediaQuery.matches);
const handler = (event: MediaQueryListEvent) => {
setMatches(event.matches);
};
mediaQuery.addEventListener('change', handler);
return () => mediaQuery.removeEventListener('change', handler);
}, [query]);
return matches;
}
function App() {
const isMobile = useMediaQuery('(max-width: 768px)');
const prefersDark = useMediaQuery('(prefers-color-scheme: dark)');
return (
<div className={prefersDark ? 'dark' : ''}>
{isMobile ? : }
);
}
Rules of Hooks
- Only call hooks at the top level - Don't call in loops, conditions, or nested functions
- Only call hooks from React functions - Call from components or custom hooks
- Start custom hook names with "use" - Convention that enables linting
function Bad({ condition }) {
if (condition) {
const [state, setState] = useState(0);
}
}
function Good({ condition }) {
const [state, setState] = useState(0);
if (condition) {
}
}
Additional References
For production-ready custom hook implementations, see:
references/custom-hooks-library.md - Complete library of reusable custom hooks