React Hooks Guide 2026: Production Patterns + Concurrent Features π
React Hooks (React 19+) power functional components with state, side effects, performance optimization, and concurrent rendering. Modern hooks like useTransition, useOptimistic, useDeferredValue, and custom hook patterns enable 60fps animations, instant server state, and Server Components integration without class component complexity.
π― Essential Hooks Hierarchy
| Hook | Purpose | When to Use |
|---|---|---|
useState | Local state | UI state, form inputs |
useEffect | Side effects | Data fetching, subscriptions |
useCallback | Memoized callbacks | Prevent child re-renders |
useMemo | Expensive calculations | Filtering, mapping large arrays |
useRef | Mutable refs | DOM access, previous values |
useTransition | Concurrent updates | Non-urgent state changes |
useOptimistic | Instant UI | Server mutations (React 19) |
ποΈ Production-Ready Hook Patterns
1. useState + useReducer (Complex State)
// Simple counter
const [count, setCount] = useState(0);
// Complex form state β useReducer
interface FormState {
name: string;
email: string;
preferences: string[];
}
type FormAction =
| { type: 'SET_FIELD'; field: keyof FormState; value: string }
| { type: 'TOGGLE_PREFERENCE'; preference: string };
const formReducer = (state: FormState, action: FormAction): FormState => {
switch (action.type) {
case 'SET_FIELD':
return { ...state, [action.field]: action.value };
case 'TOGGLE_PREFERENCE':
return {
...state,
preferences: state.preferences.includes(action.preference)
? state.preferences.filter(p => p !== action.preference)
: [...state.preferences, action.preference]
};
}
};
const [formState, dispatch] = useReducer(formReducer, {
name: '',
email: '',
preferences: []
});
2. useEffect + Cleanup (Data Fetching)
const [data, setData] = useState<Data | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
let isCancelled = false;
const fetchData = async () => {
try {
setLoading(true);
const response = await fetch('/api/users');
const result = await response.json();
if (!isCancelled) {
setData(result);
setError(null);
}
} catch (err) {
if (!isCancelled) {
setError(err instanceof Error ? err.message : 'Failed to fetch');
}
} finally {
if (!isCancelled) {
setLoading(false);
}
}
};
fetchData();
return () => {
isCancelled = true;
};
}, []);
3. useCallback + useMemo (Performance)
// β Re-creates on every render
const handleSubmit = () => { /* ... */ };
// β
Memoized callback
const handleSubmit = useCallback((formData: FormData) => {
submitForm(formData);
}, []);
// β
Expensive computation
const filteredUsers = useMemo(() => {
return users.filter(user =>
user.name.toLowerCase().includes(searchTerm.toLowerCase())
);
}, [users, searchTerm]);
// β
Previous value ref
const prevCountRef = useRef<number>();
useEffect(() => {
console.log('Count changed:', prevCountRef.current, 'β', count);
prevCountRef.current = count;
}, [count]);
π Concurrent Features (React 19)
useTransition - Non-Urgent Updates
const [isPending, startTransition] = useTransition();
const handleTabChange = (tab: string) => {
// β
Urgent: Immediate tab switch
setActiveTab(tab);
// β Non-urgent: Filter heavy list
startTransition(() => {
setFilteredUsers(filterUsers(tab));
});
};
return (
<>
<div>Tabs: {activeTab}</div>
{isPending && <Spinner />} {/* Shows during transition */}
<UserList users={filteredUsers} />
</>
);
useOptimistic - Instant Server State
const [optimisticTodos, addOptimisticTodo] = useOptimistic(
todos,
(state, newTodo: Todo) => [...state, { ...newTodo, id: 'temp', pending: true }]
);
const handleSubmit = (formData: FormData) => {
const newTodo = { title: formData.get('title') as string, done: false };
// β
Instant UI feedback
addOptimisticTodo(newTodo);
// β Network delay
createTodo(newTodo).then((result) => {
// Server result replaces optimistic state
setTodos(result);
});
};
π οΈ Custom Hooks (Production Patterns)
useAsync (Data Fetching)
type AsyncState<T> = {
data: T | null;
loading: boolean;
error: string | null;
};
function useAsync<T>(asyncFn: () => Promise<T>): AsyncState<T> {
const [state, setState] = useState<AsyncState<T>>({
data: null,
loading: true,
error: null
});
useEffect(() => {
let cancelled = false;
asyncFn()
.then(data => {
if (!cancelled) {
setState({ data, loading: false, error: null });
}
})
.catch(err => {
if (!cancelled) {
setState({ data: null, loading: false, error: err.message });
}
});
return () => {
cancelled = true;
};
}, [asyncFn]);
return state;
}
// Usage
const { data: users, loading, error } = useAsync(() =>
fetch('/api/users').then(res => res.json())
);
useLocalStorage (Persistent State)
function useLocalStorage<T>(key: string, initialValue: T): [T, (value: T) => void] {
const [storedValue, setStoredValue] = useState<T>(() => {
try {
const item = window.localStorage.getItem(key);
return item ? JSON.parse(item) : initialValue;
} catch (error) {
return initialValue;
}
});
const setValue = useCallback((value: T) => {
try {
const valueToStore = value instanceof Function ? value(storedValue) : value;
setStoredValue(valueToStore);
window.localStorage.setItem(key, JSON.stringify(valueToStore));
} catch (error) {
console.error('localStorage error:', error);
}
}, [key]);
return [storedValue, setValue];
}
β‘ Rules of Hooks (Never Break)
β Call at TOP LEVEL (not in loops/conditions) β Only in React Functions or Custom Hooks β Custom Hook names START WITH βuseβ
β Inside if/else β Inside loops β Nested functions β Regular JS functions
π― Hook Order Matters (Linter Enforced)
// β
Correct order (stable)
function MyComponent() {
const [count, setCount] = useState(0);
const [name, setName] = useState('');
useEffect(() => {}, []);
const memoizedValue = useMemo(() => {}, []);
return <div>{count}</div>;
}
// β Wrong order (breaks React)
function BadComponent({ shouldLoad }) {
if (shouldLoad) {
const [data, setData] = useState(null); // β Different order each render
}
useEffect(() => {}, []); // β
}
π Performance Patterns
| Pattern | Hook | Bundle Impact |
|---|---|---|
| Form State | useReducer | Minimal |
| Data Fetching | useEffect + AbortController | Minimal |
| Callbacks | useCallback | +2KB |
| Computations | useMemo | +1KB |
| Optimistic | useOptimistic | Built-in |
π Server Components + Hooks
// app/dashboard/page.tsx (Server Component)
async function Dashboard() {
const data = await fetchUsers();
return <ClientDashboard initialData={data} />;
}
// Client Component with hooks
'use client';
function ClientDashboard({ initialData }: { initialData: User[] }) {
const [users, setUsers] = useState(initialData);
const refresh = useCallback(async () => {
const freshData = await fetch('/api/users');
setUsers(await freshData.json());
}, []);
return (
<div>
<button onClick={refresh}>Refresh</button>
<UserList users={users} />
</div>
);
}
π― Production Checklist
β [] useCallback for expensive callbacks β [] useMemo for expensive calculations β [] useEffect cleanup prevents memory leaks β [] useTransition for non-urgent updates β [] Custom hooks for reusable logic β [] ESLint plugin-react-hooks enabled β [] Rules: Top level only, stable order β [] Server Components pass initial data β [] useOptimistic for instant feedback
π₯ Common Anti-Patterns
// β Empty dependency array (stale closures)
useEffect(() => {
fetchData(userId); // userId may be stale
}, []);
// β
Include all dependencies
useEffect(() => {
fetchData(userId);
}, [userId]);
// β
OR useCallback for stable reference
const fetchData = useCallback((id: string) => {
// fetch logic
}, []);
π― Final Thoughts
Hooks = Reactβs superpower. From simple useState to advanced concurrent patterns, hooks enable declarative data flow, performance optimization, and Server Components integration without class complexity.
2026 React workflow:
Server Components β Initial data
useOptimistic β Instant feedback
useTransition β Concurrent updates
Custom hooks β Reusable logic
60fps + instant server state
Master hooks β Master React. Build production apps with concurrent features and optimal performance π.
React 19 Docs: react.dev | Concurrent Features: react.dev/concurrent