

Hooks changed React more fundamentally than almost any release before or since — and the most consequential hook-related change in years just shipped: React Compiler hit stable v1.0 in October 2025, and it now eliminates manual useMemo and useCallback in more than 95% of real-world cases. On a production SaaS dashboard with 40+ components rendering real-time data, teams have measured roughly a 30% reduction in unnecessary re-renders just from enabling it, with zero application code changes. Understanding hooks in 2026 means understanding both how they work and which ones you increasingly don’t need to reach for by hand anymore.
useState: Data a Component Owns
useState is the most fundamental hook — it gives a function component a piece of state it can read and update, triggering a re-render whenever it changes.
function Counter() {
const [count, setCount] = useState(0);
return ;
}A detail that trips up beginners: setCount doesn’t update count immediately within the same function call — it schedules a re-render with the new value. If the next state depends on the previous one, the functional update form avoids stale-value bugs:
// Risky if called multiple times quickly — may use a stale 'count'
setCount(count + 1);
// Safe — always operates on the true current value
setCount(prevCount => prevCount + 1);useEffect: Syncing With the Outside World
useEffect runs code in response to a component rendering or specific values changing — the standard place for data fetching, subscriptions, or manually interacting with anything outside React’s own rendering system.
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
useEffect(() => {
let cancelled = false;
fetch(`/api/users/${userId}`)
.then(res => res.json())
.then(data => { if (!cancelled) setUser(data); });
return () => { cancelled = true; }; // cleanup: avoids setting state after unmount
}, [userId]); // re-runs only when userId changes
return user ? {user.name} : Loading...;
}The dependency array (the [userId] at the end) is the part that causes the most real-world bugs: missing a dependency means the effect uses stale values; including one that changes every render causes an infinite loop. ESLint’s exhaustive-deps rule catches most of these automatically and is worth enabling on any real project.

useMemo and useCallback: Increasingly Optional in 2026
These two hooks used to be essential manual performance tools — useMemo caches an expensive calculation between renders, useCallback caches a function reference so it doesn’t get recreated every render (which matters for child components wrapped in React.memo). Before React Compiler, this was standard, tedious boilerplate:
// The old, manual way
const sortedProducts = useMemo(() => {
return products.slice().sort((a, b) => a.name.localeCompare(b.name));
}, [products]);
const handleSelect = useCallback((id) => {
onSelect(id);
}, [onSelect]);With React Compiler enabled (available for React 17 and up, and recommended from day one on any new 2026 project), this memoization gets inserted automatically at build time in the vast majority of cases — the compiler analyzes the whole component and figures out what genuinely needs caching, more precisely than most developers did by hand. The hooks themselves aren’t deprecated and still work when written manually, but for new code, letting the compiler handle it is now the recommended default.
useContext: Skipping Prop Drilling
useContext lets a deeply nested component read a value directly from a provider higher up the tree, without every component in between having to pass it down as a prop.
const ThemeContext = createContext('light');
function App() {
return (
);
}
function DeeplyNestedButton() {
const theme = useContext(ThemeContext); // no props needed in between
return ;
}Custom Hooks: Extracting Reusable Logic
Any function that starts with “use” and calls other hooks internally is a custom hook — the standard way to extract and reuse stateful logic across components, without repeating the same useState/useEffect pattern in every place that needs it.
function useLocalStorage(key, initialValue) {
const [value, setValue] = useState(() => {
const stored = localStorage.getItem(key);
return stored ? JSON.parse(stored) : initialValue;
});
useEffect(() => {
localStorage.setItem(key, JSON.stringify(value));
}, [key, value]);
return [value, setValue];
}
// Now reusable anywhere: const [theme, setTheme] = useLocalStorage('theme', 'light');Frequently Asked Questions
Do I still need to learn useMemo and useCallback if React Compiler handles it automatically?
Still worth understanding conceptually, since the compiler doesn’t cover 100% of cases (some third-party library interactions or unusual patterns still need manual handling), but for most new code in 2026 you write plain functions and let the compiler optimize, rather than reaching for these hooks by default.
What’s the most common useEffect mistake?
An incorrect or missing dependency array — either omitting a value the effect actually depends on (causing stale data) or including one that changes every render (causing an infinite loop). Enabling ESLint’s exhaustive-deps rule catches most of these automatically.
When should I write a custom hook instead of just repeating useState and useEffect?
When the same stateful pattern (fetching data, syncing to localStorage, tracking window size) shows up in more than one component — extracting it into a custom hook removes duplication and keeps the logic testable in one place.
Want more React deep dives like this? Explore more Web Development articles.