You build a React app. It works. But it feels slow.
The UI stutters, clicks lag, and scrolling’s not buttery smooth. You’ve optimized your backend, images are compressed, and the Lighthouse score is decent, so what gives?
The truth is, performance issues in React apps often come from inside the house. And you don’t need a total rewrite to fix them. In this post, I’ll break down seven fast, practical wins that can instantly boost your app’s responsiveness without major refactors.
Let’s get into it.
1. Stop Unnecessary Re-Renders with React.memo
and useMemo
React’s reactivity is great… until it’s not. Components re-render way more than they need to, especially if:
- Props are changing often
- You’re passing down inline functions or objects
- You’re mapping large arrays
Quick Fix
- Wrap pure UI components in
React.memo
:
const ProductCard = React.memo(({ product }) => {
return <div>{product.name}</div>;
});
- Use
useMemo
to prevent re-creating the same array/object every render:
const filteredItems = useMemo(() => {
return items.filter(item => item.inStock);
}, [items]);
Pro tip: Use the React DevTools “Render Highlighting” feature to see what’s re-rendering too much.
2. Defer Work with useDeferredValue
or startTransition
If typing in a search input lags or freezes the UI, it’s likely because expensive filtering or rendering is happening synchronously.
React 18 was introduced useDeferredValue
and startTransition
for these situations.
Quick Fix
const deferredQuery = useDeferredValue(searchQuery);
const filteredList = useMemo(() => {
return data.filter(item => item.name.includes(deferredQuery));
}, [deferredQuery]);
Or wrap heavy updates in a transition:
startTransition(() => {
setSearchResults(heavyComputation());
});
This helps keep your app responsive even while doing background work.
3. Avoid Anonymous Functions in JSX (Especially in Lists)
This is a silent killer of performance.
Every time your component renders, it re-creates inline functions like:
<Button onClick={() => doSomething(item.id)} />
If that Button
is memoized, the memoization breaks because it sees a new function each time.
Quick Fix
Move those handlers out of the JSX:
const handleClick = useCallback(() => {
doSomething(item.id);
}, [item.id]);
<Button onClick={handleClick} />
It makes your components more predictable and stops avoidable re-renders.
4. Lazy Load Components with React.lazy
and Code Splitting
Large bundles slow everything down, even the first interaction. If you're loading modals, charts, or admin dashboards that users don’t need immediately, defer them.
Quick Fix
const LazyChart = React.lazy(() => import('./Chart'));
<Suspense fallback={<Spinner />}>
<LazyChart />
</Suspense>
5. Clean Up Uncontrolled State Bloat
Apps that feel laggy over time often suffer from state overload, especially if you're managing too much local state inside deeply nested components or abusing useState
.
Quick Fix:
- Centralized shared state with a global store like Zustand, Jotai, or even Context API (lightly).
- Avoid deeply nested state updates; flatten your component tree where possible.
- Audit where you use state, some values could just be props or derived from props.
Keep your components lean. A component should render based on props + local minimal state, not do everything itself.
6. Virtualize Long Lists with react-window
or react-virtualized
Rendering hundreds of list items at once kills performance, even on fast devices.
Instead of rendering 500 rows, just render the 10 visible ones.
Quick Win Example
import { FixedSizeList as List } from 'react-window';
<List
height={400}
itemCount={1000}
itemSize={35}
width={300}
>
{({ index, style }) => <div style={style}>Item {index}</div>}
</List>
Use When
- Rendering chat messages
- Infinite scroll
- Large tables
7. Avoid Over-Nesting Components and Complex Trees
React is fast, but deeply nested trees with props flying everywhere can cause:
- Unnecessary renders
- Poor readability
- State management chaos
Encourage “flattening” structure:
- Use composition instead of deeply embedded layouts
- Extract logic-heavy children to separate components
Final Thoughts
React apps don’t need to feel slow. Most performance issues are fixable with small, focused changes:
- Memoize more, re-render less
- Defer expensive work
- Move logic outside JSX
- Load only what’s needed
- Audit your state patterns
Before blaming React, the browser, or the user’s device, try these tweaks; you’ll likely see smoother UI, snappier feedback, and happier users.