1. React.memo for Component Memoization
Concept
Description
When to Use
Performance Impact
React.memo
HOC that memoizes component result
Expensive renders, pure components
Skips re-render if props unchanged
Shallow comparison
Default props comparison (===)
Primitive props
Fast, works for most cases
Custom comparison
Custom arePropsEqual function
Complex props, deep equality
Slower, use judiciously
When NOT to use
Simple components, always different props
Overhead > benefit
Can hurt performance
Props Type
Memo Behavior
Solution
Primitives
Compared by value
Works perfectly
Objects
Compared by reference
useMemo to stabilize
Arrays
Compared by reference
useMemo to stabilize
Functions
Compared by reference
useCallback to stabilize
Children prop
New reference each render
Extract to separate component
Example: React.memo usage patterns
// Basic React.memo usage
const ExpensiveComponent = React. memo (({ data }) => {
console. log ( 'Rendering ExpensiveComponent' );
// Expensive computation or large render tree
const processedData = data. map ( item => {
// Complex processing
return item.value * 2 ;
});
return (
< div >
{processedData. map ( val => (
< div key = {val}>{val}</ div >
))}
</ div >
);
});
// Parent component
const Parent = () => {
const [ count , setCount ] = useState ( 0 );
const [ data ] = useState ([ 1 , 2 , 3 , 4 , 5 ]);
return (
< div >
< button onClick = {() => setCount (count + 1 )}>
Count: {count}
</ button >
{ /* ExpensiveComponent won't re-render when count changes */ }
< ExpensiveComponent data = {data} />
</ div >
);
};
// ❌ BAD: memo doesn't help with new object references
const BadMemo = React. memo (({ config }) => {
return < div >{config.name}</ div >;
});
const ParentBad = () => {
const [ count , setCount ] = useState ( 0 );
return (
< div >
< button onClick = {() => setCount (count + 1 )}>{count}</ button >
{ /* Re-renders every time - new object! */ }
< BadMemo config = {{ name: 'Test' }} />
</ div >
);
};
// ✅ GOOD: stabilize object with useMemo
const GoodMemo = React. memo (({ config }) => {
return < div >{config.name}</ div >;
});
const ParentGood = () => {
const [ count , setCount ] = useState ( 0 );
const config = useMemo (() => ({ name: 'Test' }), []);
return (
< div >
< button onClick = {() => setCount (count + 1 )}>{count}</ button >
{ /* Only renders once! */ }
< GoodMemo config = {config} />
</ div >
);
};
// Custom comparison function
const UserCard = React. memo (
({ user , onEdit }) => {
return (
< div >
< h3 >{user.name}</ h3 >
< p >{user.email}</ p >
< button onClick = {() => onEdit (user.id)}>Edit</ button >
</ div >
);
},
( prevProps , nextProps ) => {
// Custom comparison: only re-render if user.id or user.name changed
return (
prevProps.user.id === nextProps.user.id &&
prevProps.user.name === nextProps.user.name
);
// Return true to skip render, false to render
}
);
// When NOT to use React.memo
const SimpleComponent = ({ text }) => {
return < div >{text}</ div >;
};
// No need for memo - component is very simple
const AlwaysChanging = ({ timestamp }) => {
return < div >{timestamp}</ div >;
};
// No need for memo - timestamp always changes
// Memoizing list items
const TodoItem = React. memo (({ todo , onToggle , onDelete }) => {
console. log ( 'Rendering todo:' , todo.id);
return (
< li >
< input
type = "checkbox"
checked = {todo.completed}
onChange = {() => onToggle (todo.id)}
/>
{todo.text}
< button onClick = {() => onDelete (todo.id)}>Delete</ button >
</ li >
);
});
const TodoList = () => {
const [ todos , setTodos ] = useState ([]);
// Stabilize callback functions
const handleToggle = useCallback (( id ) => {
setTodos ( prev => prev. map ( todo =>
todo.id === id ? { ... todo, completed: ! todo.completed } : todo
));
}, []);
const handleDelete = useCallback (( id ) => {
setTodos ( prev => prev. filter ( todo => todo.id !== id));
}, []);
return (
< ul >
{todos. map ( todo => (
< TodoItem
key = {todo.id}
todo = {todo}
onToggle = {handleToggle}
onDelete = {handleDelete}
/>
))}
</ ul >
);
};
Note: React.memo only checks props changes. It doesn't prevent re-renders from state/context
changes within the component. Use it for expensive components with stable props.
2. useMemo Hook for Expensive Calculations
Use Case
Without useMemo
With useMemo
Benefit
Expensive calculation
Runs every render
Cached until deps change
Saves computation time
Array filtering/sorting
Creates new array every render
Reuses array if deps unchanged
Prevents unnecessary work
Object creation
New reference every render
Stable reference
Prevents child re-renders
Derived state
Computed on each render
Computed only when needed
Performance optimization
When to Use
When NOT to Use
Expensive calculations (loops, recursion)
Simple calculations (arithmetic)
Processing large arrays/objects
Small data sets (< 100 items)
Stabilizing references for child props
Values that always change
Complex transformations/filtering
Trivial operations
Example: useMemo patterns
// Expensive calculation
const ProductList = ({ products , filter }) => {
// Without useMemo: runs on EVERY render (bad!)
// const filtered = products.filter(p => p.category === filter);
// With useMemo: only runs when products or filter changes
const filteredProducts = useMemo (() => {
console. log ( 'Filtering products...' );
return products. filter ( p => p.category === filter);
}, [products, filter]);
return (
< ul >
{filteredProducts. map ( p => (
< li key = {p.id}>{p.name}</ li >
))}
</ ul >
);
};
// Complex derived state
const Dashboard = ({ data }) => {
const statistics = useMemo (() => {
console. log ( 'Computing statistics...' );
const total = data. reduce (( sum , item ) => sum + item.value, 0 );
const average = total / data. length ;
const max = Math. max ( ... data. map ( item => item.value));
const min = Math. min ( ... data. map ( item => item.value));
return { total, average, max, min };
}, [data]);
return (
< div >
< p >Total: {statistics.total}</ p >
< p >Average: {statistics.average}</ p >
< p >Max: {statistics.max}</ p >
< p >Min: {statistics.min}</ p >
</ div >
);
};
// Sorting large lists
const SortableTable = ({ data , sortBy , sortOrder }) => {
const sortedData = useMemo (() => {
console. log ( 'Sorting data...' );
return [ ... data]. sort (( a , b ) => {
const aVal = a[sortBy];
const bVal = b[sortBy];
if ( typeof aVal === 'string' ) {
return sortOrder === 'asc'
? aVal. localeCompare (bVal)
: bVal. localeCompare (aVal);
}
return sortOrder === 'asc' ? aVal - bVal : bVal - aVal;
});
}, [data, sortBy, sortOrder]);
return (
< table >
{sortedData. map ( row => (
< tr key = {row.id}>
< td >{row.name}</ td >
</ tr >
))}
</ table >
);
};
// Stabilizing object references
const Parent = () => {
const [ count , setCount ] = useState ( 0 );
// ❌ BAD: new object every render
// const config = { theme: 'dark', lang: 'en' };
// ✅ GOOD: stable reference
const config = useMemo (
() => ({ theme: 'dark' , lang: 'en' }),
[]
);
return (
< div >
< button onClick = {() => setCount (count + 1 )}>{count}</ button >
< MemoizedChild config = {config} />
</ div >
);
};
// When NOT to use useMemo
const Bad = ({ a , b }) => {
// ❌ Overkill - simple calculation
const sum = useMemo (() => a + b, [a, b]);
// ✅ Just do it directly
const betterSum = a + b;
return < div >{sum}</ div >;
};
// Search and filter with multiple criteria
const AdvancedSearch = ({ items , search , filters }) => {
const filteredItems = useMemo (() => {
console. log ( 'Filtering items...' );
return items. filter ( item => {
// Text search
if (search && ! item.name. toLowerCase (). includes (search. toLowerCase ())) {
return false ;
}
// Category filter
if (filters.category && item.category !== filters.category) {
return false ;
}
// Price range
if (item.price < filters.minPrice || item.price > filters.maxPrice) {
return false ;
}
return true ;
});
}, [items, search, filters]);
const sortedItems = useMemo (() => {
console. log ( 'Sorting items...' );
return [ ... filteredItems]. sort (( a , b ) => a.name. localeCompare (b.name));
}, [filteredItems]);
return (
< ul >
{sortedItems. map ( item => (
< li key = {item.id}>{item.name} - ${item.price}</ li >
))}
</ ul >
);
};
// Recursive computation
const TreeView = ({ node }) => {
const descendantCount = useMemo (() => {
const countDescendants = ( n ) => {
if ( ! n.children) return 0 ;
return n.children. length +
n.children. reduce (( sum , child ) => sum + countDescendants (child), 0 );
};
return countDescendants (node);
}, [node]);
return < div >Descendants: {descendantCount}</ div >;
};
Warning: Don't overuse useMemo! It has overhead (memory + comparison). Profile first, optimize
later. Most calculations are fast enough without memoization.
3. useCallback Hook for Function Memoization
Scenario
Without useCallback
With useCallback
Impact
Function prop to memo component
New function = child re-renders
Stable function = no re-render
Prevents unnecessary renders
Function in useEffect deps
Effect runs every render
Effect runs only when needed
Reduces side effects
Event handlers
New function every render
Stable reference
Slight memory improvement
When to Use useCallback
When NOT to Use
Function passed to memoized child
Simple event handlers not passed down
Function in useEffect dependencies
Function only used once
Function passed to many children
Non-memoized children
Optimization after profiling
Premature optimization
Example: useCallback patterns
// Basic useCallback usage
const Parent = () => {
const [ count , setCount ] = useState ( 0 );
const [ text , setText ] = useState ( '' );
// ❌ BAD: new function every render
// const handleClick = () => {
// console.log(count);
// };
// ✅ GOOD: stable function reference
const handleClick = useCallback (() => {
console. log (count);
}, [count]); // Re-created only when count changes
return (
< div >
< input value = {text} onChange = { e => setText (e.target.value)} />
< button onClick = {() => setCount (count + 1 )}>{count}</ button >
{ /* Child won't re-render when text changes */ }
< MemoizedChild onClick = {handleClick} />
</ div >
);
};
const MemoizedChild = React. memo (({ onClick }) => {
console. log ( 'Child rendered' );
return < button onClick = {onClick}>Click me</ button >;
});
// Function in useEffect dependencies
const DataFetcher = ({ userId }) => {
const [ data , setData ] = useState ( null );
// Stable fetchData function
const fetchData = useCallback ( async () => {
const response = await fetch (\ `/api/users/ \$ {userId} \` );
const result = await response.json();
setData(result);
}, [userId]);
useEffect(() => {
fetchData();
}, [fetchData]); // Only runs when fetchData changes (i.e., when userId changes)
return <div>{data?.name}</div>;
};
// List operations with callbacks
const TodoList = () => {
const [todos, setTodos] = useState([]);
const handleToggle = useCallback((id) => {
setTodos(prev => prev.map(todo =>
todo.id === id ? { ...todo, completed: !todo.completed } : todo
));
}, []); // Empty deps - uses functional update
const handleDelete = useCallback((id) => {
setTodos(prev => prev.filter(todo => todo.id !== id));
}, []);
const handleEdit = useCallback((id, newText) => {
setTodos(prev => prev.map(todo =>
todo.id === id ? { ...todo, text: newText } : todo
));
}, []);
return (
<ul>
{todos.map(todo => (
<TodoItem
key={todo.id}
todo={todo}
onToggle={handleToggle}
onDelete={handleDelete}
onEdit={handleEdit}
/>
))}
</ul>
);
};
// When NOT to use useCallback
const SimpleComponent = () => {
const [count, setCount] = useState(0);
// ❌ Unnecessary - not passed to memoized child
const handleClick = useCallback(() => {
setCount(c => c + 1);
}, []);
// ✅ Just use inline
return (
<button onClick={() => setCount(c => c + 1)}>
{count}
</button>
);
};
// Combining useCallback with other hooks
const SearchComponent = () => {
const [query, setQuery] = useState('');
const [results, setResults] = useState([]);
const search = useCallback(async (searchQuery) => {
if (!searchQuery) {
setResults([]);
return;
}
const response = await fetch( \` /api/search?q= \$ {searchQuery} \` );
const data = await response.json();
setResults(data);
}, []);
// Debounced search
useEffect(() => {
const timer = setTimeout(() => {
search(query);
}, 300);
return () => clearTimeout(timer);
}, [query, search]);
return (
<div>
<input
value={query}
onChange={e => setQuery(e.target.value)}
/>
<ul>
{results.map(r => (
<li key={r.id}>{r.title}</li>
))}
</ul>
</div>
);
};
// useCallback with parameters from closure
const ItemList = ({ items, onUpdate }) => {
const [filter, setFilter] = useState('');
// ❌ BAD: depends on filter but not in deps
// const handleUpdate = useCallback((id, value) => {
// if (filter === 'active') {
// onUpdate(id, value);
// }
// }, []); // Missing filter!
// ✅ GOOD: includes all dependencies
const handleUpdate = useCallback((id, value) => {
if (filter === 'active') {
onUpdate(id, value);
}
}, [filter, onUpdate]);
return (
<ul>
{items.map(item => (
<Item key={item.id} item={item} onUpdate={handleUpdate} />
))}
</ul>
);
};
Note: useCallback returns memoized function, useMemo returns memoized value.
useCallback(fn, deps) is equivalent to useMemo(() => fn, deps).
4. Code Splitting with React.lazy and Suspense
Technique
Description
Bundle Impact
Use Case
React.lazy
Dynamic import for components
Separate chunk per lazy component
Route-based splitting
Suspense
Loading fallback for lazy components
No impact
Show loading state
Route-based
Split by routes/pages
One chunk per route
Most common pattern
Component-based
Split heavy components
Smaller chunks
Modals, tabs, charts
Library splitting
Separate vendor chunks
Better caching
Large dependencies
Example: Code splitting patterns
// Basic React.lazy usage
import { lazy, Suspense } from 'react' ;
const HeavyComponent = lazy (() => import ( './HeavyComponent' ));
const App = () => {
return (
< Suspense fallback = {< div >Loading...</ div >}>
< HeavyComponent />
</ Suspense >
);
};
// Route-based code splitting
import { BrowserRouter, Routes, Route } from 'react-router-dom' ;
const Home = lazy (() => import ( './pages/Home' ));
const About = lazy (() => import ( './pages/About' ));
const Dashboard = lazy (() => import ( './pages/Dashboard' ));
const Profile = lazy (() => import ( './pages/Profile' ));
const AppRouter = () => {
return (
< BrowserRouter >
< Suspense fallback = {< LoadingSpinner />}>
< Routes >
< Route path = "/" element = {< Home />} />
< Route path = "/about" element = {< About />} />
< Route path = "/dashboard" element = {< Dashboard />} />
< Route path = "/profile" element = {< Profile />} />
</ Routes >
</ Suspense >
</ BrowserRouter >
);
};
// Conditional lazy loading
const AdminPanel = lazy (() => import ( './AdminPanel' ));
const App = ({ user }) => {
return (
< div >
{user.isAdmin && (
< Suspense fallback = {< div >Loading admin panel...</ div >}>
< AdminPanel />
</ Suspense >
)}
</ div >
);
};
// Named exports with lazy
const LazyComponent = lazy (() =>
import ( './MyComponent' ). then ( module => ({
default: module .MyNamedComponent
}))
);
// Preloading lazy components
const HeavyChart = lazy (() => import ( './HeavyChart' ));
// Preload function
const preloadChart = () => {
import ( './HeavyChart' );
};
const Dashboard = () => {
const [ showChart , setShowChart ] = useState ( false );
return (
< div >
{ /* Preload on hover */ }
< button
onClick = {() => setShowChart ( true )}
onMouseEnter = {preloadChart}
>
Show Chart
</ button >
{showChart && (
< Suspense fallback = {< div >Loading chart...</ div >}>
< HeavyChart />
</ Suspense >
)}
</ div >
);
};
// Modal lazy loading
const Modal = lazy (() => import ( './Modal' ));
const App = () => {
const [ isOpen , setIsOpen ] = useState ( false );
return (
< div >
< button onClick = {() => setIsOpen ( true )}>Open Modal</ button >
{isOpen && (
< Suspense fallback = { null }>
< Modal onClose = {() => setIsOpen ( false )} />
</ Suspense >
)}
</ div >
);
};
// Tab-based splitting
const Tab1 = lazy (() => import ( './tabs/Tab1' ));
const Tab2 = lazy (() => import ( './tabs/Tab2' ));
const Tab3 = lazy (() => import ( './tabs/Tab3' ));
const TabbedInterface = () => {
const [ activeTab , setActiveTab ] = useState ( 'tab1' );
return (
< div >
< div >
< button onClick = {() => setActiveTab ( 'tab1' )}>Tab 1</ button >
< button onClick = {() => setActiveTab ( 'tab2' )}>Tab 2</ button >
< button onClick = {() => setActiveTab ( 'tab3' )}>Tab 3</ button >
</ div >
< Suspense fallback = {< div >Loading tab...</ div >}>
{activeTab === 'tab1' && < Tab1 />}
{activeTab === 'tab2' && < Tab2 />}
{activeTab === 'tab3' && < Tab3 />}
</ Suspense >
</ div >
);
};
// Error boundary with lazy loading
class ErrorBoundary extends React . Component {
state = { hasError: false };
static getDerivedStateFromError ( error ) {
return { hasError: true };
}
render () {
if ( this .state.hasError) {
return < div >Failed to load component</ div >;
}
return this .props.children;
}
}
const AppWithErrorHandling = () => {
return (
< ErrorBoundary >
< Suspense fallback = {< div >Loading...</ div >}>
< LazyComponent />
</ Suspense >
</ ErrorBoundary >
);
};
// Webpack magic comments for chunk naming
const Dashboard = lazy (() =>
import ( /* webpackChunkName: "dashboard" */ './Dashboard' )
);
const AdminPanel = lazy (() =>
import ( /* webpackChunkName: "admin" */ './AdminPanel' )
);
// Prefetching/preloading
const Settings = lazy (() =>
import (
/* webpackChunkName: "settings" */
/* webpackPrefetch: true */
'./Settings'
)
);
Note: Code splitting works best for route-based splits and heavy components (charts, editors,
admin panels). Don't split every component - bundle overhead can outweigh benefits for small components.
5. Bundle Size Analysis and Optimization
Tool
Purpose
Install/Usage
webpack-bundle-analyzer
Visualize bundle contents
npm i -D webpack-bundle-analyzer
source-map-explorer
Analyze source maps
npm i -D source-map-explorer
Bundle Buddy
Find duplicate dependencies
Upload webpack stats.json
Lighthouse
Performance audit
Chrome DevTools
Optimization
Technique
Impact
Tree shaking
Remove unused exports
Smaller bundle (10-30%)
Minification
Compress code
40-60% size reduction
Compression (gzip/brotli)
Server-side compression
60-80% transfer size reduction
Dynamic imports
Load code on demand
Faster initial load
Remove unused deps
Audit package.json
Variable savings
Lighter alternatives
Replace heavy libraries
Significant size reduction
Example: Bundle optimization techniques
// package.json - analyze bundle
{
"scripts" : {
"analyze" : "source-map-explorer 'build/static/js/*.js'" ,
"build:analyze" : "npm run build && npm run analyze"
}
}
// webpack.config.js - bundle analyzer
const BundleAnalyzerPlugin = require ( 'webpack-bundle-analyzer' ).BundleAnalyzerPlugin;
module . exports = {
plugins: [
new BundleAnalyzerPlugin ({
analyzerMode: 'static' ,
openAnalyzer: false ,
reportFilename: 'bundle-report.html'
})
]
};
// Tree shaking - import only what you need
// ❌ BAD: imports entire library
import _ from 'lodash' ;
const result = _. debounce (fn, 300 );
// ✅ GOOD: import specific function
import debounce from 'lodash/debounce' ;
const result = debounce (fn, 300 );
// Or use lodash-es for better tree shaking
import { debounce } from 'lodash-es' ;
// Replace heavy libraries with lighter alternatives
// ❌ Moment.js (large)
import moment from 'moment' ;
// ✅ date-fns (smaller, tree-shakeable)
import { format, parseISO } from 'date-fns' ;
// ✅ dayjs (smallest)
import dayjs from 'dayjs' ;
// Remove unused dependencies
// Run: npm ls
// Check: npx depcheck
// Dynamic imports for heavy dependencies
const loadChartLibrary = async () => {
const Chart = await import ( 'chart.js' );
return Chart.default;
};
// Optimize images
// Use next/image or similar
import Image from 'next/image' ;
< Image
src = "/photo.jpg"
width = { 500 }
height = { 300 }
loading = "lazy"
alt = "Photo"
/>
// Code splitting by route
const routes = [
{
path: '/' ,
component: lazy (() => import ( './pages/Home' ))
},
{
path: '/dashboard' ,
component: lazy (() => import ( './pages/Dashboard' ))
}
];
// Vendor chunk separation (webpack)
optimization : {
splitChunks : {
chunks : 'all' ,
cacheGroups : {
vendor : {
test : / [ \\ /] node_modules [ \\ /] / ,
name : 'vendors' ,
priority : 10
},
common : {
minChunks : 2 ,
priority : 5 ,
reuseExistingChunk : true
}
}
}
}
// Preload critical resources
< link rel = "preload" as = "script" href = "/main.js" />
< link rel = "prefetch" href = "/secondary.js" />
// Compression middleware (Express)
const compression = require ( 'compression' );
app. use ( compression ());
// Service worker for caching
if ( 'serviceWorker' in navigator) {
navigator.serviceWorker. register ( '/sw.js' );
}
// Remove console logs in production
// babel-plugin-transform-remove-console
{
"plugins" : [
[ "transform-remove-console" , { "exclude" : [ "error" , "warn" ] }]
]
}
Warning: Always analyze your bundle before optimizing. Focus on the biggest chunks first.
Remember: premature optimization is the root of all evil!
6. Re-render Optimization and React Profiler
Tool
Purpose
How to Use
React DevTools Profiler
Record and analyze renders
Click "Profiler" tab, record session
Highlight Updates
Visualize re-renders
Settings → "Highlight updates"
Component Stack
See render cause
Click component in Profiler
Why Did You Render
Debug unnecessary renders
npm package with detailed logs
Common Issue
Symptom
Solution
Inline functions
Child re-renders unnecessarily
useCallback + React.memo
Inline objects
Props always different
useMemo for stable reference
Context changes
All consumers re-render
Split contexts, memoize value
State too high
Entire tree re-renders
Move state closer to usage
Large lists
Slow rendering
Virtualization, pagination
No keys/bad keys
Wrong items update
Stable unique keys
Example: Debugging and fixing re-renders
// Install why-did-you-render
import React from 'react' ;
if (process.env. NODE_ENV === 'development' ) {
const whyDidYouRender = require ( '@welldone-software/why-did-you-render' );
whyDidYouRender (React, {
trackAllPureComponents: true ,
});
}
// Use in component
const MyComponent = ({ value }) => {
return < div >{value}</ div >;
};
MyComponent.whyDidYouRender = true ;
// Finding render causes with useEffect
const DebugComponent = ({ prop1 , prop2 , prop3 }) => {
useEffect (() => {
console. log ( 'Component rendered' );
console. log ( 'prop1:' , prop1);
console. log ( 'prop2:' , prop2);
console. log ( 'prop3:' , prop3);
});
return < div >Debug</ div >;
};
// Custom hook to track prop changes
const useWhyDidYouUpdate = ( name , props ) => {
const previousProps = useRef ();
useEffect (() => {
if (previousProps.current) {
const allKeys = Object. keys ({ ... previousProps.current, ... props });
const changedProps = {};
allKeys. forEach ( key => {
if (previousProps.current[key] !== props[key]) {
changedProps[key] = {
from: previousProps.current[key],
to: props[key]
};
}
});
if (Object. keys (changedProps). length > 0 ) {
console. log ( '[why-did-you-update]' , name, changedProps);
}
}
previousProps.current = props;
});
};
// Usage
const MyComponent = ( props ) => {
useWhyDidYouUpdate ( 'MyComponent' , props);
return < div >{props.value}</ div >;
};
// Fix: Inline object props
// ❌ BAD
const Parent = () => {
return < Child style = {{ color: 'red' }} />; // New object every render
};
// ✅ GOOD
const Parent = () => {
const style = useMemo (() => ({ color: 'red' }), []);
return < Child style = {style} />;
};
// Fix: Context re-renders
// ❌ BAD: value changes every render
const ThemeProvider = ({ children }) => {
const [ theme , setTheme ] = useState ( 'light' );
return (
< ThemeContext.Provider value = {{ theme, setTheme }}>
{children}
</ ThemeContext.Provider >
);
};
// ✅ GOOD: stable value reference
const ThemeProvider = ({ children }) => {
const [ theme , setTheme ] = useState ( 'light' );
const value = useMemo (
() => ({ theme, setTheme }),
[theme]
);
return (
< ThemeContext.Provider value = {value}>
{children}
</ ThemeContext.Provider >
);
};
// Fix: State colocation
// ❌ BAD: state too high
const App = () => {
const [ formData , setFormData ] = useState ({});
return (
< div >
< Header /> { /* Re-renders on formData change */ }
< Sidebar /> { /* Re-renders on formData change */ }
< Form data = {formData} onChange = {setFormData} />
</ div >
);
};
// ✅ GOOD: state colocated
const App = () => {
return (
< div >
< Header />
< Sidebar />
< FormContainer /> { /* State inside here */ }
</ div >
);
};
const FormContainer = () => {
const [ formData , setFormData ] = useState ({});
return < Form data = {formData} onChange = {setFormData} />;
};
// Profiling with React.Profiler API
const App = () => {
const onRenderCallback = (
id , // component name
phase , // "mount" or "update"
actualDuration , // time spent rendering
baseDuration , // estimated time without memoization
startTime , // when started
commitTime , // when committed
interactions // Set of interactions
) => {
console. log (\ ` \$ {id} took \$ {actualDuration}ms to render \` );
};
return (
<Profiler id="App" onRender={onRenderCallback}>
<MyApp />
</Profiler>
);
};