Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import React, { useState, useCallback } from 'react';

/**
* CODING EXERCISE 3: useCallback and Unnecessary Re-renders
*
* PROBLEM:
* In the code below, the ChildComponent re-renders every time the parent's count changes,
* even though the child only depends on the handleClick function.
*
* Options:
* A) ChildComponent re-renders only when button is clicked
* B) ChildComponent re-renders every time count changes
* C) ChildComponent never re-renders
* D) React throws an error about missing dependencies
*
* BONUS: How would you prevent unnecessary re-renders of ChildComponent?
*/

// Child component that should only re-render when its props change
const ChildComponent = ({ onClick }) => {
console.log('ChildComponent rendered');
return (
<div style={{ padding: '10px', backgroundColor: '#e3f2fd', marginTop: '10px', borderRadius: '5px' }}>
<p>I'm a child component</p>
<button onClick={onClick}>Click me from child</button>
</div>
);
};

function Problem() {
const [count, setCount] = useState(0);
const [childClicks, setChildClicks] = useState(0);

// This function is recreated on every render
const handleClick = () => {
setChildClicks(prev => prev + 1);
};

return (
<div style={{ padding: '20px', fontFamily: 'Arial' }}>
<h2>Exercise 3: useCallback & Memoization Problem</h2>

<div style={{ marginBottom: '20px' }}>
<p>Parent Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment Parent Count</button>
</div>

<div style={{ marginBottom: '20px' }}>
<p>Child Clicks: {childClicks}</p>
</div>

<ChildComponent onClick={handleClick} />

<div style={{
marginTop: '20px',
padding: '10px',
backgroundColor: '#fff3cd',
border: '1px solid #ffc107',
borderRadius: '5px'
}}>
<p><strong>⚠️ Question:</strong> What happens when you click "Increment Parent Count"?</p>
<p>Think about:</p>
<ul>
<li>Does the ChildComponent re-render?</li>
<li>Why does it re-render (or not)?</li>
<li>How can you prevent unnecessary re-renders?</li>
<li>Check the console to see render logs</li>
</ul>
<p style={{ fontSize: '12px', color: '#856404' }}>
<strong>Tip:</strong> Open the browser console and click the parent button multiple times
</p>
</div>
</div>
);
}

export default Problem;
Original file line number Diff line number Diff line change
@@ -0,0 +1,274 @@
import React, { useState, useCallback, memo } from 'react';

/**
* CODING EXERCISE 3: useCallback and Unnecessary Re-renders - SOLUTION
*
* ANSWER: B) ChildComponent re-renders every time count changes
*
* EXPLANATION:
*
* 1. FUNCTION REFERENCE PROBLEM:
* - Every time the parent re-renders, handleClick is recreated
* - Even though the function does the same thing, it's a NEW reference
* - React compares props by reference: oldFunction !== newFunction
*
* 2. WHY IT RE-RENDERS:
* - Parent count changes → Parent re-renders
* - Parent re-render → handleClick recreated with new reference
* - New reference → Child sees "different" prop → Child re-renders
*
* 3. THE SOLUTION:
* - Use useCallback to memoize the function
* - Use React.memo to prevent re-renders when props haven't changed
* - Combine both for optimal performance
*/

// Problem: Regular child component (re-renders on every parent render)
const RegularChild = ({ onClick, label }) => {
console.log(`${label} rendered`);
return (
<div style={{ padding: '10px', backgroundColor: '#ffe6e6', marginTop: '10px', borderRadius: '5px' }}>
<p><strong>{label}</strong></p>
<button onClick={onClick}>Click me</button>
</div>
);
};

// Solution 1: Memoized child (still re-renders because function reference changes)
const MemoizedChild = memo(({ onClick, label }) => {
console.log(`${label} rendered`);
return (
<div style={{ padding: '10px', backgroundColor: '#fff3e6', marginTop: '10px', borderRadius: '5px' }}>
<p><strong>{label}</strong></p>
<button onClick={onClick}>Click me</button>
</div>
);
});

// Solution 2: Optimized child (won't re-render unnecessarily)
const OptimizedChild = memo(({ onClick, label }) => {
console.log(`${label} rendered`);
return (
<div style={{ padding: '10px', backgroundColor: '#e6ffe6', marginTop: '10px', borderRadius: '5px' }}>
<p><strong>{label}</strong></p>
<button onClick={onClick}>Click me</button>
</div>
);
});

function Solution() {
const [count, setCount] = useState(0);
const [clicks1, setClicks1] = useState(0);
const [clicks2, setClicks2] = useState(0);
const [clicks3, setClicks3] = useState(0);

// ❌ WRONG: Function recreated on every render
const handleClick1 = () => {
setClicks1(prev => prev + 1);
};

// ⚠️ PARTIAL: Function recreated on every render (memo doesn't help)
const handleClick2 = () => {
setClicks2(prev => prev + 1);
};

// ✅ CORRECT: Function memoized with useCallback
const handleClick3 = useCallback(() => {
setClicks3(prev => prev + 1);
}, []); // Empty deps = function never changes

return (
<div style={{ padding: '20px', fontFamily: 'Arial' }}>
<h2>Exercise 3: useCallback & Memoization Solution</h2>

<div style={{
marginBottom: '20px',
padding: '15px',
backgroundColor: '#e3f2fd',
borderRadius: '5px'
}}>
<h3>Parent State</h3>
<p>Parent Count: {count}</p>
<button onClick={() => setCount(count + 1)}>
Increment Parent Count (Watch Console!)
</button>
</div>

{/* Example 1: Regular child without memo */}
<div style={{ marginBottom: '20px' }}>
<h3>❌ Problem: Regular Child (No Optimization)</h3>
<p>Clicks: {clicks1}</p>
<RegularChild
onClick={handleClick1}
label="Regular Child (Always Re-renders)"
/>
<pre style={{
backgroundColor: '#fff',
padding: '10px',
borderRadius: '3px',
fontSize: '12px',
overflow: 'auto'
}}>
{`const handleClick = () => {
setClicks(prev => prev + 1);
};

<RegularChild onClick={handleClick} />

// Problem: Function recreated every render
// Child re-renders every time`}
</pre>
</div>

{/* Example 2: Memoized child but function still recreated */}
<div style={{ marginBottom: '20px' }}>
<h3>⚠️ Partial: Memo Child (Still Re-renders)</h3>
<p>Clicks: {clicks2}</p>
<MemoizedChild
onClick={handleClick2}
label="Memoized Child (Still Re-renders)"
/>
<pre style={{
backgroundColor: '#fff',
padding: '10px',
borderRadius: '3px',
fontSize: '12px',
overflow: 'auto'
}}>
{`const MemoizedChild = memo(({ onClick }) => {
return <button onClick={onClick}>Click</button>;
});

const handleClick = () => { /* ... */ };
<MemoizedChild onClick={handleClick} />

// Problem: memo() helps, but function still
// recreated, so props still "change"`}
</pre>
</div>

{/* Example 3: Optimized with useCallback + memo */}
<div style={{ marginBottom: '20px' }}>
<h3>✅ Solution: useCallback + memo</h3>
<p>Clicks: {clicks3}</p>
<OptimizedChild
onClick={handleClick3}
label="Optimized Child (No Unnecessary Re-renders)"
/>
<pre style={{
backgroundColor: '#fff',
padding: '10px',
borderRadius: '3px',
fontSize: '12px',
overflow: 'auto'
}}>
{`const OptimizedChild = memo(({ onClick }) => {
return <button onClick={onClick}>Click</button>;
});

const handleClick = useCallback(() => {
setClicks(prev => prev + 1);
}, []); // Memoized - same reference

<OptimizedChild onClick={handleClick} />

// ✅ Function reference stays the same
// ✅ memo() prevents re-render
// ✅ Only re-renders when actually needed`}
</pre>
</div>

{/* Key Takeaways */}
<div style={{
padding: '15px',
backgroundColor: '#f0f0f0',
borderRadius: '5px',
marginTop: '20px'
}}>
<h3>📚 Key Takeaways</h3>
<ol>
<li><strong>Functions are recreated:</strong> On every render, function declarations create new references</li>
<li><strong>Reference equality:</strong> React compares props using === (reference comparison)</li>
<li><strong>useCallback:</strong> Memoizes function references between renders</li>
<li><strong>React.memo:</strong> Prevents re-renders when props haven't changed</li>
<li><strong>Combine both:</strong> useCallback + memo for optimal performance</li>
<li><strong>Dependencies matter:</strong> useCallback deps determine when function updates</li>
</ol>
</div>

{/* When to Use */}
<div style={{
marginTop: '20px',
padding: '15px',
backgroundColor: '#e6f3ff',
borderRadius: '5px'
}}>
<h3>💡 When to Use useCallback</h3>
<ul>
<li>✅ Passing callbacks to memoized child components</li>
<li>✅ Function is a dependency in useEffect/useMemo</li>
<li>✅ Expensive child components that re-render often</li>
<li>❌ Simple components without performance issues</li>
<li>❌ Functions that change on every render anyway</li>
<li>❌ Premature optimization (measure first!)</li>
</ul>
</div>

{/* Common Mistakes */}
<div style={{
marginTop: '20px',
padding: '15px',
backgroundColor: '#fff3cd',
borderRadius: '5px'
}}>
<h3>⚠️ Common Mistakes</h3>
<ul>
<li>❌ Using useCallback without React.memo on child</li>
<li>❌ Forgetting dependencies (causes stale closures)</li>
<li>❌ Over-optimizing everything (adds complexity)</li>
<li>❌ Using useCallback for functions that change often</li>
<li>✅ Profile first, optimize when needed</li>
<li>✅ Use React DevTools Profiler to find bottlenecks</li>
</ul>
</div>

{/* Code Comparison */}
<div style={{
marginTop: '20px',
padding: '15px',
backgroundColor: '#f8f9fa',
borderRadius: '5px'
}}>
<h3>📊 Performance Comparison</h3>
<table style={{ width: '100%', borderCollapse: 'collapse' }}>
<thead>
<tr style={{ backgroundColor: '#e9ecef' }}>
<th style={{ padding: '10px', textAlign: 'left', border: '1px solid #dee2e6' }}>Approach</th>
<th style={{ padding: '10px', textAlign: 'left', border: '1px solid #dee2e6' }}>Re-renders</th>
<th style={{ padding: '10px', textAlign: 'left', border: '1px solid #dee2e6' }}>Performance</th>
</tr>
</thead>
<tbody>
<tr>
<td style={{ padding: '10px', border: '1px solid #dee2e6' }}>Regular Child</td>
<td style={{ padding: '10px', border: '1px solid #dee2e6' }}>❌ Every parent render</td>
<td style={{ padding: '10px', border: '1px solid #dee2e6' }}>Poor</td>
</tr>
<tr>
<td style={{ padding: '10px', border: '1px solid #dee2e6' }}>memo() only</td>
<td style={{ padding: '10px', border: '1px solid #dee2e6' }}>❌ Every parent render</td>
<td style={{ padding: '10px', border: '1px solid #dee2e6' }}>Poor</td>
</tr>
<tr>
<td style={{ padding: '10px', border: '1px solid #dee2e6' }}>useCallback + memo</td>
<td style={{ padding: '10px', border: '1px solid #dee2e6' }}>✅ Only when needed</td>
<td style={{ padding: '10px', border: '1px solid #dee2e6' }}>Excellent</td>
</tr>
</tbody>
</table>
</div>
</div>
);
}

export default Solution;
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { default as Problem } from './Problem';
export { default as Solution } from './Solution';
10 changes: 10 additions & 0 deletions coding-exercise/src/exercises/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import * as Exercise01 from './exercise-01-state-batching';
import * as Exercise02 from './exercise-02-useeffect-dependencies';
import * as Exercise03 from './exercise-03-useCallback-memoization';

/**
* Exercise Registry
Expand Down Expand Up @@ -30,6 +31,15 @@ export const exercises = [
Problem: Exercise02.Problem,
Solution: Exercise02.Solution,
},
{
id: 'exercise-03',
title: 'useCallback & Memoization',
description: 'Prevent unnecessary re-renders with useCallback and React.memo',
difficulty: 'Medium',
topics: ['useCallback', 'React.memo', 'Performance', 'Re-renders'],
Problem: Exercise03.Problem,
Solution: Exercise03.Solution,
},
// Add more exercises here...
];

Expand Down