Let's look at how to stop writing legacy class components and fully adopt modern React hooks with the patterns from the official react.dev docs.
What you'll learn:
- Why modern React dropped class components for hooks
- How to structure state with
useStateand derived values - How to avoid common rendering pitfalls
- How to use interactive sandboxes to test React APIs
Why the Shift to Function Components
For years, developers learned React twice: first with classes, then with hooks. That creates unnecessary overhead. The modern React ecosystem relies entirely on function components and hooks.
When you write components as pure functions, your UI becomes a direct projection of your state. Here is a simple counter hook example replacing old class state methods:
import { useState } from 'react';
export default function Counter() {
const [count, setCount] = useState(0);
function handleClick() {
setCount(prev => prev + 1);
}
return (
<button onClick={handleClick}>
Clicked {count} times
</button>
);
}
Speed Note: Always use the updater function pattern (
setCount(prev => prev + 1)) when your next state depends on the previous state to avoid stale closures.
Managing Complex State with History
As apps grow, simple state is not enough. You often need to track history, like undo/redo stacks or game moves.
Here is how you manage an array of states using useState without mutating your original history arrays:
import { useState } from 'react';
export default function GameHistory() {
const [history, setHistory] = useState([Array(9).fill(null)]);
const [currentMove, setCurrentMove] = useState(0);
const currentSquares = history[currentMove];
const xIsNext = currentMove % 2 === 0;
function handlePlay(nextSquares: (string | null)[]) {
const nextHistory = [...history.slice(0, currentMove + 1), nextSquares];
setHistory(nextHistory);
setCurrentMove(nextHistory.length - 1);
}
return (
<div className="p-4">
<p className="font-bold">Current turn: {xIsNext ? 'X' : 'O'}</p>
<p>Move #{currentMove}</p>
</div>
);
}
Gotcha: Never mutate state directly (
squares[i] = 'X'). Always slice or spread arrays to create new references so React knows when to trigger a re-render.
Thinking in Components and Props
Breaking down a complex user interface starts with identifying boundaries. Instead of massive monolithic components, break your UI into single-responsibility pieces.
Here is how you separate a reusable Square component from its parent Board:
import React from 'react';
interface SquareProps {
value: string | null;
onSquareClick: () => void;
}
export function Square({ value, onSquareClick }: SquareProps) {
return (
<button
className="h-12 w-12 border border-gray-300 text-xl font-bold flex items-center justify-center bg-white hover:bg-gray-100"
onClick={onSquareClick}
>
{value}
</button>
);
}
Accessibility: Ensure buttons containing only symbols or icons include an
aria-labelattribute so screen readers can describe their action accurately.
Leveraging Built-in API References
When building custom components, don't guess API signatures. The new API Reference section on react.dev splits documentation into two clear segments: Reference (formal signatures and return values) and Usage (real-world recipes).
Whenever you reach for a hook like useState, useEffect, or useMemo, check the interactive examples to see canonical use cases before writing custom abstractions.
import { useState, useMemo } from 'react';
export default function FilteredList({ items }: { items: string[] }) {
const [query, setQuery] = useState('');
const filteredItems = useMemo(() => {
return items.filter(item => item.toLowerCase().includes(query.toLowerCase()));
}, [items, query]);
return (
<div>
<input
value={query}
onChange={e => setQuery(e.target.value)}
placeholder="Filter items..."
className="border p-2 rounded"
/>
<ul className="mt-2">
{filteredItems.map((item, index) => (
<li key={index}>{item}</li>
))}
</ul>
</div>
);
}
Summary
Migrating your mental model to modern React means fully embracing function components, predictable state management, and clear component boundaries. Using the interactive examples and reference guides on react.dev will speed up your daily development workflow.
Next Steps:
- Explore the interactive challenges in the "Learn React" section of
react.dev. - Audit your existing codebase for legacy class components and refactor them to hooks.
- Bookmark the API Reference pages for quick syntax lookups during feature builds.