React Performance Optimization: Memoization, Code Splitting & Profiling
Practical techniques to eliminate unnecessary re-renders, optimize heavy state trees, and boost React app responsiveness.

- 1.Why React Apps Get Slow
- 2.1. When to Use `useMemo` and `useCallback`
- 3.2. Preventing Re-renders with `React.memo`
- 4.3. State Colocation: The Secret to Fast React Apps
- 5.5. Eliminating Anonymous Function Prop Re-creations
- 6.6. React Performance Optimization Checklist
- 7.7. Optimizing Context API & State Stores (Zustand)
- 8.8. Frequently Asked Questions (FAQ)
Why React Apps Get Slow#
React's declarative component model makes building user interfaces intuitive. However, as applications grow, common patterns cause performance degradation: unnecessary component re-renders, large uncompressed bundles, and heavy synchronous calculations blocking the browser's UI thread.
This guide provides practical, actionable techniques to optimize React applications.
1. When to Use `useMemo` and `useCallback`#
In React, every time a parent component re-renders, all child components re-render by default, and all internal functions and objects are recreated.
When to use `useMemo`: Use `useMemo` only for computationally expensive calculations (like filtering or sorting 1,000+ items):
import { useMemo } from "react";
export function ArticleList({ articles, searchQuery }: { articles: Article[]; searchQuery: string }) {
// Memoize filtered results so it only recalculates when articles or query changes
const filteredArticles = useMemo(() => {
return articles.filter((article) =>
article.title.toLowerCase().includes(searchQuery.toLowerCase())
);
}, [articles, searchQuery]);
return (
<div className="grid gap-4">
{filteredArticles.map((a) => (
<ArticleCard key={a.slug} article={a} />
))}
</div>
);
}2. Preventing Re-renders with `React.memo`#
Wrap pure visual components with React.memo so they only re-render if their props change:
import React from "react";
interface TagPillProps {
label: string;
onClick: (tag: string) => void;
}
export const TagPill = React.memo(function TagPill({ label, onClick }: TagPillProps) {
return (
<button
onClick={() => onClick(label)}
className="px-3 py-1 bg-gray-100 rounded-full text-xs hover:bg-gray-200"
>
#{label}
</button>
);
});3. State Colocation: The Secret to Fast React Apps#
The #1 cause of unnecessary re-renders is placing local UI state too high in the component tree.
Rule of Thumb: Keep state as close as possible to the component that actually uses it. If only a modal dialog cares whether it is open or closed, keep isOpen state inside the modal trigger, not in the global root layout!
5. Eliminating Anonymous Function Prop Re-creations#
A common source of unnecessary re-renders in React is passing inline arrow functions to memoized child components:
// BAD: Recreates a new function on every single render, breaking React.memo!
<MemoizedUserCard user={user} onDelete={() => handleDelete(user.id)} />
// GOOD: Stabilize the handler with useCallback
const handleDeleteUser = useCallback((userId: string) => {
setUsers((prev) => prev.filter((u) => u.id !== userId));
}, []);
<MemoizedUserCard user={user} onDelete={handleDeleteUser} />6. React Performance Optimization Checklist#
- [ ] Use React DevTools Profiler to identify components rendering longer than 16ms.
- [ ] Colocate state: keep local modal and dropdown state inside isolated leaf components.
- [ ] Use
useMemoonly for expensive sorting/filtering on 500+ items. - [ ] Use
useCallbackwhen passing callbacks toReact.memochild components. - [ ] Lazy-load heavy dialogs and interactive charting components using
next/dynamic.
7. Optimizing Context API & State Stores (Zustand)#
React Context is designed for low-frequency state changes (such as theme toggles or user authentication). Using React Context for high-frequency updates (such as mouse coordinates, form inputs, or live WebSocket tickers) triggers re-renders across all consuming components.
The Solution: Granular Selectors with Zustand Modern React applications use lightweight stores like **Zustand**, which support atomic selector subscriptions:
import { create } from "zustand";
interface AppStore {
searchQuery: string;
isSidebarOpen: boolean;
setSearchQuery: (query: string) => void;
toggleSidebar: () => void;
}
export const useAppStore = create<AppStore>((set) => ({
searchQuery: "",
isSidebarOpen: false,
setSearchQuery: (searchQuery) => set({ searchQuery }),
toggleSidebar: () => set((state) => ({ isSidebarOpen: !state.isSidebarOpen })),
}));
// Component only re-renders when searchQuery changes, ignoring isSidebarOpen!
export function SearchInput() {
const searchQuery = useAppStore((state) => state.searchQuery);
const setSearchQuery = useAppStore((state) => state.setSearchQuery);
return (
<input
type="text"
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
className="p-2 border rounded"
/>
);
}8. Frequently Asked Questions (FAQ)#
Q: Does React.memo do a deep or shallow comparison of props? By default, React.memo performs a **shallow comparison** of complex props (objects, arrays, functions). If you pass a new object literal like `style={{ color: 'red' }}` or an inline array on every render, the shallow equality check fails and the component will still re-render. Always extract static objects outside component bodies!
Q: Should I wrap every single React component in React.memo? No. Memoization has a computational cost (storing previous props in memory and checking equality on every render). For simple presentational components with lightweight DOM trees, standard re-rendering is faster than the overhead of memoization.

Published by
Vyuhantrix Team
Web & Systems Engineering · Vyuhantrix
Vyuhantrix is an open technology learning platform based in Ahmedabad, India, publishing step-by-step programming tutorials, system design breakdowns, and free developer tools.
Keep Learning
Recommended Guides
The Definitive Full-Stack Web Development Roadmap (2026 Edition)
A complete step-by-step masterclass covering modern HTML5/CSS, TypeScript, Next.js App Router, Server Components, API Design, and Cloud Edge Deployments.
Mastering React Server Components in Next.js 15: A Complete Guide
A deep dive into React Server Components, how they differ from Client Components, and how Next.js 15 leverages them to achieve zero-bundle-size rendering, streaming, and superior Core Web Vitals.
Next.js Server Actions: Complete Guide to Full-Stack Mutations in 2026
A comprehensive guide to Next.js Server Actions — how they work, form handling, progressive enhancement, optimistic updates, error boundaries, and integrating with databases and external APIs without exposing API routes.