React Performance Optimization: From Good to Great
Discover proven techniques to optimize React applications for maximum performance, from code splitting to advanced rendering strategies.

React Performance Optimization: From Good to Great
Performance is crucial for user experience. Let's explore advanced techniques to make your React applications blazingly fast.
Understanding React's Rendering
Before optimizing, understand how React renders components:
function ParentComponent() {
const [count, setCount] = useState(0)
return (
<div>
<button onClick={() => setCount(count + 1)}>
Count: {count}
</button>
<ExpensiveComponent /> {/* Re-renders on every count change! */}
</div>
)
}
React.memo for Component Memoization
Prevent unnecessary re-renders with React.memo
:
const ExpensiveComponent = React.memo(({ data }) => {
console.log('Rendering ExpensiveComponent')
return (
<div>
{/* Expensive rendering logic */}
</div>
)
})
// Now it only re-renders when props change
Custom Comparison Function
const MyComponent = React.memo(
({ user }) => {
return <div>{user.name}</div>
},
(prevProps, nextProps) => {
// Return true if props are equal (don't re-render)
return prevProps.user.id === nextProps.user.id
}
)
useMemo and useCallback Hooks
Optimize expensive calculations and function references:
useMemo for Expensive Computations
function ProductList({ products, filter }) {
const filteredProducts = useMemo(() => {
console.log('Filtering products...')
return products.filter(p => p.category === filter)
}, [products, filter])
return (
<div>
{filteredProducts.map(product => (
<ProductCard key={product.id} product={product} />
))}
</div>
)
}
useCallback for Stable Function References
function ParentComponent() {
const [count, setCount] = useState(0)
const handleClick = useCallback(() => {
console.log('Button clicked')
}, []) // Dependencies array
return (
<div>
<ChildComponent onClick={handleClick} />
</div>
)
}
Code Splitting with React.lazy
Load components only when needed:
import { lazy, Suspense } from 'react'
const HeavyComponent = lazy(() => import('./HeavyComponent'))
function App() {
return (
<Suspense fallback={<div>Loading...</div>}>
<HeavyComponent />
</Suspense>
)
}
Route-based Code Splitting
import { lazy, Suspense } from 'react'
import { BrowserRouter, Routes, Route } from 'react-router-dom'
const Home = lazy(() => import('./pages/Home'))
const About = lazy(() => import('./pages/About'))
const Contact = lazy(() => import('./pages/Contact'))
function App() {
return (
<BrowserRouter>
<Suspense fallback={<LoadingSpinner />}>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/contact" element={<Contact />} />
</Routes>
</Suspense>
</BrowserRouter>
)
}
Virtualization for Large Lists
Use virtualization for rendering large lists efficiently:
import { FixedSizeList } from 'react-window'
function VirtualizedList({ items }) {
const Row = ({ index, style }) => (
<div style={style}>
{items[index].name}
</div>
)
return (
<FixedSizeList
height={600}
itemCount={items.length}
itemSize={50}
width="100%"
>
{Row}
</FixedSizeList>
)
}
Optimize State Management
Keep state as local as possible:
// ❌ Bad: Lifting state unnecessarily
function App() {
const [selectedId, setSelectedId] = useState(null)
return (
<div>
<Sidebar selectedId={selectedId} />
<Content selectedId={selectedId} onChange={setSelectedId} />
</div>
)
}
// ✅ Good: State where it's needed
function Content() {
const [selectedId, setSelectedId] = useState(null)
return (
<div>
{/* Use state locally */}
</div>
)
}
Use Production Builds
Always use production builds for deployment:
# Create optimized production build
npm run build
# The production build:
# - Minifies code
# - Removes development warnings
# - Optimizes React's performance
Performance Monitoring
Use React DevTools Profiler to identify bottlenecks:
import { Profiler } from 'react'
function onRenderCallback(
id, // Component identifier
phase, // "mount" or "update"
actualDuration, // Time spent rendering
) {
console.log(`${id} took ${actualDuration}ms to render`)
}
function App() {
return (
<Profiler id="App" onRender={onRenderCallback}>
<YourComponents />
</Profiler>
)
}
Key Takeaways
- Measure first - Use profiling tools before optimizing
- Memoize wisely - Don't over-optimize, it can hurt performance
- Code split - Reduce initial bundle size
- Virtualize lists - Render only visible items
- Keep state local - Avoid unnecessary re-renders
Conclusion
React performance optimization is an iterative process. Start with measurements, identify bottlenecks, and apply the right techniques. Your users will thank you with better engagement and lower bounce rates.
Remember: premature optimization is the root of all evil. Optimize when you have real performance problems, not just because you can.