Hooks (Core) 🧠
Hooks let function components manage state, side effects, performance, and shared logic. Use them thoughtfully to keep UI predictable.
1) useEffect Deep Dive 🔄
Correct Patterns ✅
- Keep effects focused on a single concern (fetching, subscriptions, timers).
- Always list every reactive value you use in the dependency array.
- Guard asynchronous work with cleanup to avoid stale state.
useEffect(() => {
let cancelled = false;
async function loadProfile() {
const response = await fetch(`/api/users/${userId}`);
if (!cancelled) setProfile(await response.json());
}
loadProfile();
return () => {
cancelled = true;
};
}, [userId]);Pitfalls ❌
- Fetching inside render instead of effects causes infinite loops.
- Omitting dependencies hides bugs; rely on ESLint to warn you.
- Doing heavy work in effects without throttling can freeze the UI.
2) useRef for DOM and Mutable Values 🧱
useRefstores a.currentproperty that persists across renders without causing re-renders.- Use for DOM nodes, timers, or memoized mutable values.
import { useEffect, useRef } from "react";
function FocusInput() {
const inputRef = useRef(null);
useEffect(() => {
inputRef.current?.focus();
}, []);
return <input ref={inputRef} placeholder="Auto focus" />;
}- Store previous values:
const prevCount = useRef(count); useEffect(() => { prevCount.current = count; });
3) useMemo & useCallback ⚙️
useMemo(fn, deps)caches a value;useCallback(fn, deps)caches a function.- Use them to avoid expensive recalculations or unnecessary prop changes only when profiling reveals a bottleneck.
const filteredTodos = useMemo(() => {
return todos.filter(todo => todo.done === showDone);
}, [todos, showDone]);
const handleSelect = useCallback(id => {
onSelect(id);
}, [onSelect]);🧠 Rule: optimize when a component re-renders too often or when child components rely on referential equality.
4) useReducer for Complex State 🧠
- Prefer when state transitions follow clear actions.
- Keeps updates predictable and colocated with logic.
const initialState = { count: 0 };
function reducer(state, action) {
switch (action.type) {
case "increment":
return { count: state.count + 1 };
case "reset":
return initialState;
default:
throw new Error("Unknown action");
}
}
const [state, dispatch] = useReducer(reducer, initialState);- Dispatch actions from UI:
dispatch({ type: "increment" });
5) Custom Hooks 🔁
- Extract reusable logic into functions starting with
use...(enforce hook rules). - Share logic without HOCs or render props.
import { useEffect, useState } from "react";
function useOnlineStatus() {
const [online, setOnline] = useState(navigator.onLine);
useEffect(() => {
function update() {
setOnline(navigator.onLine);
}
window.addEventListener("online", update);
window.addEventListener("offline", update);
return () => {
window.removeEventListener("online", update);
window.removeEventListener("offline", update);
};
}, []);
return online;
}
function StatusBadge() {
const online = useOnlineStatus();
return <span>{online ? "🟢 Online" : "🔴 Offline"}</span>;
}Key Takeaways ✅
useEffectshould be deterministic: list dependencies, clean up side effects.useRefstores data that does not trigger renders (DOM nodes, previous values).useMemo/useCallbackare performance tools; use sparingly and measure impact.useReducerorganizes complex state transitions; custom hooks package logic for reuse.
Recap 🔄
Hooks are composable primitives: useEffect handles side effects, useRef preserves mutable values, memo hooks optimize re-renders, useReducer structures complex state, and custom hooks let you share behavior cleanly. Use them intentionally to keep components tidy and predictable.
Hook Reference 🗂️
useState— store local component state.useEffect— run side effects after render.useContext— read values from the nearest matching Provider.useReducer— manage complex state transitions with actions.useCallback— memoize function references between renders.useMemo— cache expensive computations.useRef— hold mutable values or DOM nodes without re-rendering.useId— generate stable IDs for accessibility attributes.useLayoutEffect— run synchronous effects after DOM mutations (before paint).useInsertionEffect— inject critical styles before layout calculations.useImperativeHandle— customize refs exposed by components.useDebugValue— label custom hooks in React DevTools.useDeferredValue— postpone rendering a value to keep UI responsive.useTransition— mark state updates as non-urgent for smoother UX.useSyncExternalStore— subscribe to external stores with concurrent safety.useOptimistic— show optimistic UI values while awaiting confirmation.
Last updated on