Introduction
Unnecessary Re-renders in React - Beginner's Guide
What is a Re-render
A re-render is when React calls your component function again to create a new version of the UI. React does this when: - State changes - Props change - Parent component re-renders
Think of it like this
- Your component is like a recipe - Re-render is like cooking the recipe again - Sometimes you need to cook again (state changed) - Sometimes you cook unnecessarily (nothing changed!)
What are Unnecessary Re-renders
Unnecessary re-renders happen when React re-renders a component even though nothing actually changed. This wastes performance and can slow down your app.
Real-world analogy
Imagine a restaurant
- Necessary re-render: Customer orders new dish → Cook makes it (needed!) - Unnecessary re-render: Customer just looks at menu → Cook makes dish anyway (wasteful!)
Why do we care
- Performance: Unnecessary re-renders slow down your app - User experience: App feels laggy - Resources: Wastes CPU and battery - Cost: More server resources in production
COMPLETE BEGINNER'S GUIDE TO UNNECESSARY RE-RENDERS
1. WHAT IS A RE-RENDER
A re-render is when React calls your component function again to create a new version of the UI. This happens when: - State changes (useState) - Props change - Parent component re-renders
Think of it like cooking
- Component = Recipe - Re-render = Cooking the recipe again - Sometimes needed (new order) - Sometimes wasteful (same order)
2. WHAT ARE UNNECESSARY RE-RENDERS
Unnecessary re-renders happen when React re-renders a component even though nothing actually changed. This wastes performance.
Example
- Parent has count state - Child doesn't use count - Parent re-renders → Child re-renders (unnecessary!)
3. WHAT CAUSES UNNECESSARY RE-RENDERS
Cause 1: New Objects/Arrays in Render
❌ Bad: New object every render
function Component() {
const user = { name: 'John' };
return <Child user={user} />;
}
✅ Good: Stable object
const user = { name: 'John' }; // Outside component
// OR
const user = useMemo(() => ({ name: 'John' }), []);
Cause 2: New Functions in Render
❌ Bad: New function every render
function Component() {
const handleClick = () => console.log('clicked');
return <Child onClick={handleClick} />;
}
✅ Good: Stable function
const handleClick = useCallback(() => {
console.log('clicked');
}, []);
Cause 3: Parent Re-renders All Children
❌ Bad: All children re-render
function Parent() {
const [count, setCount] = useState(0);
return (
<>
<Child1 /> // Re-renders when count changes
<Child2 /> // Re-renders when count changes
</>
);
}
✅ Good: Memoized children
const Child1 = memo(() => <div>Child 1</div>);
const Child2 = memo(() => <div>Child 2</div>);
Cause 4: Inline Styles/Objects
❌ Bad: New style object every render
<div style={{ color: 'red' }} />
✅ Good: Stable style
const style = useMemo(() => ({ color: 'red' }), []);
<div style={style} />
How to Identify Re-renders
Method 1: Console.log
function Component() {
console.log('Component rendered');
return <div>...</div>;
}
Method 2: useEffect
useEffect(() => {
console.log('Component rendered');
});
Method 3: React DevTools Profiler
Install React DevTools, open Profiler tab, record and see which components re-render.
Solutions
Solution 1: useMemo (for objects/arrays)
const user = useMemo(() => ({
name: 'John',
age: 25
}), []); // Only recreate when deps change
Solution 2: useCallback (for functions)
const handleClick = useCallback(() => {
console.log('clicked');
}, []); // Only recreate when deps change
Solution 3: React.memo (for components)
const Child = memo(function Child({ name }) {
return <div>{name}</div>;
}); // Only re-render when props change
Solution 4: Split Components
Split state into separate components so only affected components re-render.
React uses REFERENCE EQUALITY to check if props changed.
Even if values are the same
- New object = Different reference = Re-render - New array = Different reference = Re-render - New function = Different reference = Re-render That's why we need useMemo, useCallback, and memo!
- Component re-renders frequently - Component is expensive to render - You see performance issues - Profiler shows it's a problem - Component is simple - Re-renders are rare - No performance issues
- Premature optimization
Imagine a restaurant
NECESSARY RE-RENDER
- Customer orders pizza - Kitchen makes pizza - Pizza delivered (needed!)
UNNECESSARY RE-RENDER
- Customer just looks at menu - Kitchen makes pizza anyway - Pizza wasted (wasteful!) Remember: Not all re-renders are bad! Only optimize when you
have a performance problem. Premature optimization can make code harder to read and maintain.
Example 1: Understanding Re-renders (Basic)
Complete Code Example
// ============================================
// ============================================
function UnderstandingRerenders() {
const [count, setCount] = useState(0);
const [name, setName] = useState('Vimal');
// This runs every time the component re-renders
console.log('🔄 Component re-rendered!', { count, name });
return (
<div style={{ padding: '20px', border: '2px solid #ff6b6b', marginBottom: '20px' }}>
<h2>Example 1: Understanding Re-renders</h2>
<p style={{ color: '#666', fontSize: '14px', marginBottom: '15px' }}>
Open console to see when re-renders happen. Every time you change
count or name, the component re-renders.
</p>
<p><strong>Count:</strong> {count}</p>
<p><strong>Name:</strong> {name}</p>
<button onClick={() => setCount(count + 1)} style={{ marginRight: '10px', padding: '8px 16px' }}>
Increment Count
</button>
<input
value={name}
onChange={(e) => setName(e.target.value)}
placeholder="Type your name"
style={{ padding: '8px' }}
/>
<p style={{ color: '#ff6b6b', fontSize: '13px', marginTop: '10px' }}>
⚠️ Check console: Component re-renders when count OR name changes.
<br />
This is NORMAL and EXPECTED - state changed, so re-render is needed!
</p>
</div>
);
}
// ============================================
Example 2: Unnecessary Re-render - New Objects/Arrays
⚠️ Problem: Creating new object on every render
Complete Code Example
// ============================================
function UnnecessaryRerenderObjects() {
const [count, setCount] = useState(0);
// ❌ PROBLEM: Creating new object on every render
// Even though the values are the same, it's a NEW object
// React sees it as "different" and re-renders child
const user = {
name: 'Vimal',
age: 25
};
return (
<div style={{ padding: '20px', border: '2px solid #ff6b6b', marginBottom: '20px' }}>
<h2>Example 2: Unnecessary Re-render - New Objects</h2>
<p style={{ color: '#666', fontSize: '14px', marginBottom: '15px' }}>
❌ Creating new objects/arrays in render causes unnecessary re-renders
of child components, even if the values are the same.
</p>
<p><strong>Count:</strong> {count}</p>
<button onClick={() => setCount(count + 1)} style={{ padding: '8px 16px' }}>
Increment (doesn't affect user, but child still re-renders!)
</button>
{/* Child component will re-render every time, even though user data didn't change */}
<UserDisplay user={user} />
</div>
);
}
function UserDisplay({ user }) {
console.log('🔄 UserDisplay re-rendered!', user);
return (
<div style={{ marginTop: '15px', padding: '10px', backgroundColor: '#ffe0e0', borderRadius: '4px' }}>
<p><strong>User:</strong> {user.name}, Age: {user.age}</p>
<p style={{ fontSize: '12px', color: '#ff6b6b' }}>
⚠️ Check console: This re-renders even when count changes!
<br />
The user object is "new" every time, so React thinks it changed.
</p>
</div>
);
}
// ============================================
Example 3: Solution - useMemo for Objects
✅ Solution: useMemo creates object only when dependencies change
If count changes but user data doesn't, object stays the same
Complete Code Example
// ============================================
function SolutionUseMemo() {
const [count, setCount] = useState(0);
// ✅ SOLUTION: useMemo creates object only when dependencies change
// If count changes but user data doesn't, object stays the same
const user = useMemo(() => ({
name: 'Vimal',
age: 25
}), []); // Empty array = never changes
return (
<div style={{ padding: '20px', border: '2px solid #51cf66', marginBottom: '20px' }}>
<h2>Example 3: Solution - useMemo for Objects</h2>
<p style={{ color: '#666', fontSize: '14px', marginBottom: '15px' }}>
✅ Using useMemo to create stable object reference.
Child only re-renders when user actually changes.
</p>
<p><strong>Count:</strong> {count}</p>
<button onClick={() => setCount(count + 1)} style={{ padding: '8px 16px' }}>
Increment
</button>
<UserDisplayMemo user={user} />
</div>
);
}
const UserDisplayMemo = memo(function UserDisplayMemo({ user }) {
console.log('🔄 UserDisplayMemo re-rendered!', user);
return (
<div style={{ marginTop: '15px', padding: '10px', backgroundColor: '#e0ffe0', borderRadius: '4px' }}>
<p><strong>User:</strong> {user.name}, Age: {user.age}</p>
<p style={{ fontSize: '12px', color: '#51cf66' }}>
✅ Check console: This only re-renders when user object actually changes!
</p>
</div>
);
});
// ============================================
Example 4: Unnecessary Re-render - New Functions
⚠️ Problem: Creating new function on every render
Even though the function does the same thing, it's a NEW function
Complete Code Example
// ============================================
function UnnecessaryRerenderFunctions() {
const [count, setCount] = useState(0);
// ❌ PROBLEM: Creating new function on every render
// Even though the function does the same thing, it's a NEW function
// React sees it as "different" and re-renders child
const handleClick = () => {
console.log('Button clicked!');
};
return (
<div style={{ padding: '20px', border: '2px solid #ff6b6b', marginBottom: '20px' }}>
<h2>Example 4: Unnecessary Re-render - New Functions</h2>
<p style={{ color: '#666', fontSize: '14px', marginBottom: '15px' }}>
❌ Creating new functions in render causes unnecessary re-renders
of child components that receive them as props.
</p>
<p><strong>Count:</strong> {count}</p>
<button onClick={() => setCount(count + 1)} style={{ padding: '8px 16px' }}>
Increment
</button>
<ButtonWithHandler onClick={handleClick} />
</div>
);
}
function ButtonWithHandler({ onClick }) {
console.log('🔄 ButtonWithHandler re-rendered!');
return (
<div style={{ marginTop: '15px' }}>
<button onClick={onClick} style={{ padding: '8px 16px' }}>
Click Me
</button>
<p style={{ fontSize: '12px', color: '#ff6b6b', marginTop: '5px' }}>
⚠️ Check console: This re-renders every time count changes!
<br />
The onClick function is "new" every time, so React thinks it changed.
</p>
</div>
);
}
// ============================================
Example 5: Solution - useCallback for Functions
✅ Solution: useCallback creates stable function reference
Complete Code Example
// ============================================
function SolutionUseCallback() {
const [count, setCount] = useState(0);
// ✅ SOLUTION: useCallback creates stable function reference
// Function only changes when dependencies change
const handleClick = useCallback(() => {
console.log('Button clicked!');
}, []); // Empty array = never changes
return (
<div style={{ padding: '20px', border: '2px solid #51cf66', marginBottom: '20px' }}>
<h2>Example 5: Solution - useCallback for Functions</h2>
<p style={{ color: '#666', fontSize: '14px', marginBottom: '15px' }}>
✅ Using useCallback to create stable function reference.
Child only re-renders when function actually changes.
</p>
<p><strong>Count:</strong> {count}</p>
<button onClick={() => setCount(count + 1)} style={{ padding: '8px 16px' }}>
Increment
</button>
<ButtonWithHandlerMemo onClick={handleClick} />
</div>
);
}
const ButtonWithHandlerMemo = memo(function ButtonWithHandlerMemo({ onClick }) {
console.log('🔄 ButtonWithHandlerMemo re-rendered!');
return (
<div style={{ marginTop: '15px' }}>
<button onClick={onClick} style={{ padding: '8px 16px' }}>
Click Me
</button>
<p style={{ fontSize: '12px', color: '#51cf66', marginTop: '5px' }}>
✅ Check console: This only re-renders when onClick function actually changes!
</p>
</div>
);
});
// ============================================
Example 6: Unnecessary Re-render - Parent Re-renders All Children
Complete Code Example
// ============================================
function ParentRerendersAll() {
const [count, setCount] = useState(0);
const [name, setName] = useState('Vimal');
return (
<div style={{ padding: '20px', border: '2px solid #ff6b6b', marginBottom: '20px' }}>
<h2>Example 6: Parent Re-renders All Children</h2>
<p style={{ color: '#666', fontSize: '14px', marginBottom: '15px' }}>
❌ When parent re-renders, ALL children re-render too, even if
their props didn't change.
</p>
<p><strong>Count:</strong> {count}</p>
<p><strong>Name:</strong> {name}</p>
<button onClick={() => setCount(count + 1)} style={{ marginRight: '10px', padding: '8px 16px' }}>
Increment Count
</button>
<input
value={name}
onChange={(e) => setName(e.target.value)}
placeholder="Type name"
style={{ padding: '8px' }}
/>
{/* These children re-render even though their props don't change */}
<ExpensiveChild title="Child 1" />
<ExpensiveChild title="Child 2" />
<ExpensiveChild title="Child 3" />
</div>
);
}
function ExpensiveChild({ title }) {
console.log(`🔄 ${title} re-rendered!`);
// Simulate expensive operation
const start = performance.now();
while (performance.now() - start < 1) {
// Waste time
}
return (
<div style={{ marginTop: '10px', padding: '10px', backgroundColor: '#ffe0e0', borderRadius: '4px' }}>
<p>{title}</p>
<p style={{ fontSize: '12px', color: '#ff6b6b' }}>
⚠️ Check console: All children re-render when parent state changes!
</p>
</div>
);
}
// ============================================
Example 7: Solution - React.memo
Complete Code Example
// ============================================
function SolutionReactMemo() {
const [count, setCount] = useState(0);
const [name, setName] = useState('Vimal');
return (
<div style={{ padding: '20px', border: '2px solid #51cf66', marginBottom: '20px' }}>
<h2>Example 7: Solution - React.memo</h2>
<p style={{ color: '#666', fontSize: '14px', marginBottom: '15px' }}>
✅ Using React.memo to prevent unnecessary re-renders.
Children only re-render when their props actually change.
</p>
<p><strong>Count:</strong> {count}</p>
<p><strong>Name:</strong> {name}</p>
<button onClick={() => setCount(count + 1)} style={{ marginRight: '10px', padding: '8px 16px' }}>
Increment Count
</button>
<input
value={name}
onChange={(e) => setName(e.target.value)}
placeholder="Type name"
style={{ padding: '8px' }}
/>
{/* These children won't re-render if their props don't change */}
<ExpensiveChildMemo title="Child 1" />
<ExpensiveChildMemo title="Child 2" />
<ExpensiveChildMemo title="Child 3" />
</div>
);
}
const ExpensiveChildMemo = memo(function ExpensiveChildMemo({ title }) {
console.log(`🔄 ${title} re-rendered!`);
// Simulate expensive operation
const start = performance.now();
while (performance.now() - start < 1) {
// Waste time
}
return (
<div style={{ marginTop: '10px', padding: '10px', backgroundColor: '#e0ffe0', borderRadius: '4px' }}>
<p>{title}</p>
<p style={{ fontSize: '12px', color: '#51cf66' }}>
✅ Check console: Children only re-render when their props change!
</p>
</div>
);
});
// ============================================
Example 8: Inline Styles and Objects
✅ Solution: Define styles outside component or use useMemo
Complete Code Example
// ============================================
function InlineStylesProblem() {
const [count, setCount] = useState(0);
return (
<div style={{ padding: '20px', border: '2px solid #ff6b6b', marginBottom: '20px' }}>
<h2>Example 8: Inline Styles and Objects</h2>
<p style={{ color: '#666', fontSize: '14px', marginBottom: '15px' }}>
❌ Inline styles create new objects on every render, causing
unnecessary re-renders of children.
</p>
<p><strong>Count:</strong> {count}</p>
<button onClick={() => setCount(count + 1)} style={{ padding: '8px 16px' }}>
Increment
</button>
{/* ❌ New style object every render */}
<div style={{ marginTop: '15px', padding: '10px', backgroundColor: '#ffe0e0', borderRadius: '4px' }}>
<p style={{ color: '#ff6b6b' }}>This has inline styles</p>
<p style={{ fontSize: '12px' }}>
⚠️ The style object is recreated every render!
</p>
</div>
</div>
);
}
function InlineStylesSolution() {
const [count, setCount] = useState(0);
// ✅ SOLUTION: Define styles outside component or use useMemo
const boxStyle = useMemo(() => ({
marginTop: '15px',
padding: '10px',
backgroundColor: '#e0ffe0',
borderRadius: '4px'
}), []);
return (
<div style={{ padding: '20px', border: '2px solid #51cf66', marginBottom: '20px' }}>
<h2>Example 9: Solution - Stable Style Objects</h2>
<p style={{ color: '#666', fontSize: '14px', marginBottom: '15px' }}>
✅ Using useMemo to create stable style objects.
</p>
<p><strong>Count:</strong> {count}</p>
<button onClick={() => setCount(count + 1)} style={{ padding: '8px 16px' }}>
Increment
</button>
<div style={boxStyle}>
<p style={{ color: '#51cf66' }}>This has stable styles</p>
<p style={{ fontSize: '12px' }}>
✅ The style object is created once and reused!
</p>
</div>
</div>
);
}
// ============================================
Example 10: Common Causes Summary
Complete Code Example
// ============================================
function CommonCausesSummary() {
return (
<div style={{ padding: '20px', border: '2px solid #845ef7', marginBottom: '20px' }}>
<h2>Example 10: Common Causes of Unnecessary Re-renders</h2>
<div style={{ marginTop: '15px' }}>
<h3>❌ Common Causes:</h3>
<ul style={{ fontSize: '14px', lineHeight: '1.8' }}>
<li><strong>New objects in render:</strong> <code>{`const obj = { a: 1 }`}</code></li>
<li><strong>New arrays in render:</strong> <code>{`const arr = [1, 2, 3]`}</code></li>
<li><strong>New functions in render:</strong> <code>{`const fn = () => {}`}</code></li>
<li><strong>Inline styles:</strong> <code>{`style={{ color: 'red' }}`}</code></li>
<li><strong>Parent re-renders:</strong> All children re-render</li>
<li><strong>Context value changes:</strong> All consumers re-render</li>
</ul>
</div>
<div style={{ marginTop: '15px' }}>
<h3>✅ Solutions:</h3>
<ul style={{ fontSize: '14px', lineHeight: '1.8' }}>
<li><strong>useMemo:</strong> For objects and arrays</li>
<li><strong>useCallback:</strong> For functions</li>
<li><strong>React.memo:</strong> For components</li>
<li><strong>Move styles outside:</strong> Or use useMemo</li>
<li><strong>Split components:</strong> Isolate state changes</li>
<li><strong>Context splitting:</strong> Separate contexts</li>
</ul>
</div>
<div style={{
marginTop: '20px',
padding: '15px',
backgroundColor: '#fff3cd',
borderRadius: '8px'
}}>
<h3 style={{ marginTop: 0 }}>💡 Key Principle:</h3>
<p style={{ fontSize: '14px', lineHeight: '1.8' }}>
React uses <strong>reference equality</strong> to check if props changed.
Even if values are the same, if it's a new object/array/function,
React thinks it changed and re-renders.
</p>
</div>
</div>
);
}
// ============================================
Example 11: How to Identify Re-renders
Complete Code Example
// ============================================
function HowToIdentify() {
const [count, setCount] = useState(0);
// Method 1: Console.log
console.log('🔄 Component rendered, count:', count);
// Method 2: useEffect (runs after render)
useEffect(() => {
console.log('📊 useEffect ran - component just rendered');
});
return (
<div style={{ padding: '20px', border: '2px solid #339af0', marginBottom: '20px' }}>
<h2>Example 11: How to Identify Re-renders</h2>
<p style={{ color: '#666', fontSize: '14px', marginBottom: '15px' }}>
Ways to detect when components re-render:
</p>
<div style={{ marginBottom: '15px' }}>
<h3>Method 1: Console.log</h3>
<pre style={{
backgroundColor: '#f8f9fa',
padding: '10px',
borderRadius: '4px',
fontSize: '12px',
overflow: 'auto'
}}>
{`function Component() {
console.log('Component rendered');
return <div>...</div>;
}`}
</pre>
</div>
<div style={{ marginBottom: '15px' }}>
<h3>Method 2: useEffect</h3>
<pre style={{
backgroundColor: '#f8f9fa',
padding: '10px',
borderRadius: '4px',
fontSize: '12px',
overflow: 'auto'
}}>
{`useEffect(() => {
console.log('Component rendered');
});`}
</pre>
</div>
<div style={{ marginBottom: '15px' }}>
<h3>Method 3: React DevTools Profiler</h3>
<p style={{ fontSize: '14px' }}>
Use React DevTools Profiler to see which components re-render
and how long they take.
</p>
</div>
<p><strong>Count:</strong> {count}</p>
<button onClick={() => setCount(count + 1)} style={{ padding: '8px 16px' }}>
Increment (check console)
</button>
<p style={{ color: '#339af0', fontSize: '13px', marginTop: '10px' }}>
✅ Open console to see render logs. Click the button to see re-render.
</p>
</div>
);
}
// ============================================
// Main App Component
// ============================================
function App() {
return (
<div style={{
padding: '20px',
fontFamily: 'Arial, sans-serif',
maxWidth: '1200px',
margin: '0 auto'
}}>
<h1>Unnecessary Re-renders in React - Complete Beginner's Guide</h1>
<div style={{
marginBottom: '30px',
padding: '20px',
backgroundColor: '#e3f2fd',
borderRadius: '8px'
}}>
<h2>Key Concepts:</h2>
<ul style={{ fontSize: '14px', lineHeight: '1.8' }}>
<li><strong>Re-render:</strong> React calls component function again</li>
<li><strong>Necessary:</strong> State/props changed → re-render needed</li>
<li><strong>Unnecessary:</strong> Nothing changed → re-render wasted</li>
<li><strong>Cause:</strong> New objects/arrays/functions on every render</li>
<li><strong>Solution:</strong> useMemo, useCallback, React.memo</li>
</ul>
</div>
<UnderstandingRerenders />
<UnnecessaryRerenderObjects />
<SolutionUseMemo />
<UnnecessaryRerenderFunctions />
<SolutionUseCallback />
<ParentRerendersAll />
<SolutionReactMemo />
<InlineStylesProblem />
<InlineStylesSolution />
<CommonCausesSummary />
<HowToIdentify />
</div>
);
}
export default App;
Key Takeaways
Remember: Not all re-renders are bad! Only optimize when you
have a performance problem. Premature optimization can make
code harder to read and maintain.
- ✅ Re-renders happen when state/props change
- ✅ Unnecessary re-renders waste performance
- ✅ New objects/arrays/functions cause re-renders
- ✅ useMemo, useCallback, memo prevent unnecessary re-renders
- ✅ Only optimize when needed (don't over-optimize)
Conclusion
In this comprehensive guide, we've explored unnecessary re-renders in React and learned how to identify and fix them. We covered what causes unnecessary re-renders (new objects, arrays, functions on every render), how to identify them using console logs, useEffect, and React DevTools Profiler, and the solutions available (useMemo, useCallback, React.memo).
Remember: Not all re-renders are bad! React re-renders when state or props change, which is necessary and expected. The key is to identify and fix unnecessary re-renders - when components re-render even though nothing actually changed. Use useMemo for objects and arrays, useCallback for functions, and React.memo for components, but only when you have a measurable performance problem. Premature optimization can make code harder to read and maintain. Practice with the examples provided and use React DevTools Profiler to identify real performance bottlenecks in your applications.