React Best Practices
React is forgiving enough to let genuinely problematic patterns work without any obvious error — components that re-render far more than necessary, state that's structured in a way that causes bugs down the line. These practices are the ones that consistently separate maintainable React codebases from ones that get harder to work in as they grow.
Last updated 2026-09-08
Keep components small and focused on one responsibility
A component handling data-fetching, complex conditional rendering, and deeply nested markup all at once becomes difficult to test, reuse, or reason about. Splitting by responsibility — a container component for logic, presentational components for markup — keeps each piece understandable in isolation.
Derive state instead of duplicating it
Storing a value in state that could instead be computed from existing state or props during render creates a synchronization problem — the derived value can drift out of sync with its source. Compute it directly during render instead of storing a redundant copy.
const fullName = `${firstName} ${lastName}`; // computed during renderconst [fullName, setFullName] = useState(''); // separate state that must be manually kept in syncUse keys correctly in lists — never the array index for items that can reorder
React uses the key prop to track which list item is which across re-renders. Using the array index as a key works fine for a static list, but breaks (causing wrong state or lost input) the moment items can be reordered, inserted, or removed — use a stable, unique identifier from the actual data instead.
Avoid unnecessary useEffect for things that can be computed during render
A common overuse pattern is syncing one piece of state to another with useEffect, when the second value could simply be computed directly during render. Effects should be reserved for genuinely external synchronization — subscriptions, DOM manipulation, or fetching data — not for keeping internal state in sync with itself.
Memoize expensive computations and callbacks deliberately, not by default
useMemo and useCallback have their own overhead and add code complexity. Reach for them when you've identified an actual, measured performance problem — an expensive computation re-running unnecessarily, or a callback causing a child to re-render — rather than wrapping everything preemptively.
Colocate state as close as possible to where it's used
Lifting state higher than necessary 'just in case' forces more of the component tree to re-render on every update and makes data flow harder to trace. Keep state at the lowest common ancestor that actually needs it, lifting only when genuinely shared.
Handle loading and error states explicitly for async data
A component that only renders correctly once data has successfully loaded, with no handling for the loading or error case, produces a blank screen or a crash under any less-than-perfect network condition — which real users hit far more often than a happy-path demo suggests.
Common Mistakes to Avoid
- ⚠Using array index as a key for a list that can reorder, causing state to attach to the wrong item
- ⚠Storing derived values in state instead of computing them during render
- ⚠Reaching for useEffect to sync state that could simply be computed directly
- ⚠Wrapping every function in useCallback and every value in useMemo without a measured need
- ⚠Prop-drilling deeply instead of using context or component composition for widely-needed data
- ⚠No explicit loading/error UI for async data, leaving a blank or broken screen under real network conditions
Frequently Asked Questions
Should I always use TypeScript with React?
Not strictly required, but strongly recommended for anything beyond a small project — catching prop-type mismatches and null/undefined issues at compile time prevents an entire category of runtime bugs that are otherwise easy to miss until a user hits them.
Are class components still worth learning?
Functional components with hooks are the standard for new code today, but understanding class components is still useful for maintaining older codebases, since a large amount of existing production React code was written before hooks existed.
When should I actually reach for useMemo or useCallback?
When you've identified — through profiling, not guessing — that a specific computation is expensive enough to matter, or that a specific callback identity change is causing an expensive child re-render. Applying them everywhere by default adds complexity without guaranteed benefit.