React Performance Optimization: Tips and Tricks for 2025
React is fast by default, but as applications grow, performance can degrade. Here are modern techniques to keep your React apps blazing fast.
Understanding React Rendering
React re-renders components when:
- State changes
- Props change
- Parent component re-renders
Optimization Techniques
1. Memoization with useMemo and useCallback
import { useMemo, useCallback } from 'react';
function ProductList({ products, onSelect }) {
// Memoize expensive calculations
const sortedProducts = useMemo(() => {
return products.sort((a, b) => b.price - a.price);
}, [products]);
// Memoize callback functions
const handleClick = useCallback((id) => {
onSelect(id);
}, [onSelect]);
return (
<div>
{sortedProducts.map(p => (
<Product key={p.id} data={p} onClick={handleClick} />
))}
</div>
);
}
2. React.memo for Component Memoization
import { memo } from 'react';
const ExpensiveComponent = memo(({ data }) => {
return <div>{/* Complex rendering logic */}</div>;
});
3. Code Splitting with lazy and Suspense
import { lazy, Suspense } from 'react';
const Dashboard = lazy(() => import('./Dashboard'));
const Settings = lazy(() => import('./Settings'));
function App() {
return (
<Suspense fallback={<Loading />}>
<Dashboard />
</Suspense>
);
}
4. Virtual Scrolling
For long lists, use virtual scrolling:
import { FixedSizeList } from 'react-window';
function VirtualList({ items }) {
return (
<FixedSizeList
height={600}
itemCount={items.length}
itemSize={50}
width="100%"
>
{({ index, style }) => (
<div style={style}>{items[index].name}</div>
)}
</FixedSizeList>
);
}
React 19 Concurrent Features
1. useTransition
Mark non-urgent updates:
import { useState, useTransition } from 'react';
function SearchResults() {
const [isPending, startTransition] = useTransition();
const [query, setQuery] = useState('');
const handleChange = (e) => {
startTransition(() => {
setQuery(e.target.value);
});
};
return (
<div>
<input onChange={handleChange} />
{isPending && <Spinner />}
<Results query={query} />
</div>
);
}
2. useDeferredValue
Defer expensive computations:
import { useState, useDeferredValue } from 'react';
function ProductSearch({ products }) {
const [search, setSearch] = useState('');
const deferredSearch = useDeferredValue(search);
const filtered = products.filter(p =>
p.name.includes(deferredSearch)
);
return (
<div>
<input value={search} onChange={e => setSearch(e.target.value)} />
<ProductList products={filtered} />
</div>
);
}
Best Practices
1. Avoid Inline Objects and Functions
// Bad
<Component style={{ margin: 10 }} onClick={() => doSomething()} />
// Good
const style = { margin: 10 };
const handleClick = () => doSomething();
<Component style={style} onClick={handleClick} />
2. Key Props
Use stable, unique keys:
// Bad
{items.map((item, index) => <Item key={index} data={item} />)}
// Good
{items.map(item => <Item key={item.id} data={item} />)}
3. Bundle Size Optimization
- Use tree shaking
- Analyze bundle with webpack-bundle-analyzer
- Import only what you need
// Bad
import _ from 'lodash';
// Good
import debounce from 'lodash/debounce';
Performance Monitoring
React DevTools Profiler
- Install React DevTools
- Use Profiler tab
- Record interactions
- Analyze render times
Web Vitals
import { getCLS, getFID, getFCP, getLCP, getTTFB } from 'web-vitals';
getCLS(console.log);
getFID(console.log);
getFCP(console.log);
getLCP(console.log);
getTTFB(console.log);
Conclusion
React performance optimization is about understanding when and why components re-render. Use memoization wisely, leverage concurrent features, and always measure before optimizing. Remember: premature optimization is the root of all evil—profile first, then optimize!