Skip to content

Commit 92448bd

Browse files
Add blog post on optimizing React re-renders for better performance (#265)
* add blog post on optimizing React re-renders for better performance * refactor: update TodoList component to use item IDs as keys and return null in MemoizedList * publish: finalize and publish blog post on optimizing React re-renders with new examples and insights * refactor: clean up whitespace in the throttling example and enhance clarity in the blog post on React re-renders * refactor: improve useThrottledWindowWidth hook for better SSR compatibility and performance in React re-renders blog post * refactor: update TodoApp to include unique IDs for todos and remove unnecessary console log in TodoList * feat: add author profile for Ajith Kumar and update blog post date with content improvements * feat: add Ajith Kumar's profile image to author section
1 parent 6f1e8ff commit 92448bd

File tree

7 files changed

+246
-0
lines changed

7 files changed

+246
-0
lines changed
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
---
2+
title: Ajith Kumar
3+
image: '/authors/ajith-kumar/ajith-kumar.jpg'
4+
subtitle: 'Software Craftsperson @Incubyte πŸ’Ό | Ruby on Rails Expert πŸ’Ž | React Enthusiast βš›οΈ | Performance Optimizer πŸš€ | Tech Writer ✍️'
5+
---
6+
7+
Passionate Software Craftsperson with deep expertise in Ruby on Rails and a growing appreciation for React in a more sensible, performance-focused way. Always exploring new ways to write cleaner, more efficient code and build robust applications.
8+
9+
Loves sharing knowledge through technical writing and helping developers build better applications. When not coding, you'll find me diving deep into Rails internals, performance optimization techniques, tending to my garden, working on aquascaping, or watching anime. πŸŒ±πŸ πŸ“Ί
10+
11+
Connect with me on [LinkedIn](https://www.linkedin.com/in/ajithbuddy) or follow my coding journey on [GitHub](https://github.com/Ajith-kumar-in).
34.6 KB
Loading
Lines changed: 235 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,235 @@
1+
+++
2+
title = "Taming React Re-renders: A Guide to Optimizing Performance"
3+
slug = "taming-react-re-renders"
4+
date = 2025-10-05T20:48:34+05:30
5+
image = "/images/2025/taming-react-re-renders/header.jpg"
6+
draft = false
7+
authors = ["Ajith Kumar"]
8+
description = "A comprehensive guide to understanding and optimizing React re-renders for better application performance"
9+
tags = ["React", "Performance", "Software Craftsmanship"]
10+
categories = ["React", "Performance", "Software Craftsmanship"]
11+
type = ""
12+
+++
13+
14+
Have you ever noticed your React app feeling sluggish? or wondered why your components keep re-rendering when they shouldn't? You're not alone!
15+
16+
## The Problem: Unnecessary Re-renders
17+
18+
Let's start with a simple example. Imagine you're building a todo list app:
19+
20+
```jsx
21+
function TodoApp() {
22+
const [todos, setTodos] = useState([]);
23+
const [text, setText] = useState('');
24+
25+
const addTodo = () => {
26+
const newTodo = {
27+
id: Date.now(),
28+
text: text
29+
};
30+
setTodos([...todos, newTodo]);
31+
setText('');
32+
};
33+
34+
return (
35+
<div>
36+
<input
37+
value={text}
38+
onChange={(e) => setText(e.target.value)}
39+
placeholder='Add todo…'
40+
/>
41+
<button onClick={addTodo}>Add</button>
42+
<TodoList items={todos} />
43+
</div>
44+
);
45+
}
46+
47+
function TodoList({items}) {
48+
return (
49+
<ul>
50+
{items.map((item) => (
51+
<li key={item.id}>{item.text}</li>
52+
))}
53+
</ul>
54+
);
55+
}
56+
```
57+
58+
Notice something interesting? Every time you type in the input field, the entire `TodoList` re-renders, even though you haven't added any new todos! This happens because React re-renders the parent component (`TodoApp`) when its state changes, which then re-renders all its children.
59+
60+
## Understanding React's Lifecycle
61+
62+
To fix unnecessary re-renders, we need to understand how React works. Think of React components like a tree:
63+
64+
- When a component first appears on screen, it **mounts**
65+
- When its data (state or props) changes, it **updates**
66+
- When it's removed from the screen, it **unmounts**
67+
68+
Here's what you need to know:
69+
70+
- **State changes** trigger re-renders
71+
72+
{{< figure src="/images/2025/taming-react-re-renders/state-changes-example.jpg" caption="" >}}
73+
74+
When a component's state changes, React automatically re-renders that component. This is React's way of keeping the UI in sync with your data. For example, when you type in an input field, the component holding that input's state will re-render to reflect the new value.
75+
76+
- **Parent updates** cause child re-renders
77+
78+
{{< figure src="/images/2025/taming-react-re-renders/parent-example.jpg" caption="" >}}
79+
80+
React follows a top-down rendering pattern. When a parent component re-renders, all of its children re-render too (unless they're memoized). This is why moving state down the component tree can be so effective - it limits the scope of re-renders to only the components that actually need to update.
81+
82+
- React is smart! It batches multiple state updates in event handlers into a single re-render
83+
84+
## Watch Out for Custom Hooks!
85+
86+
Custom hooks are great for reusing logic, but they can cause performance issues behind your back. Here's an example:
87+
88+
```jsx
89+
function useWindowWidth() {
90+
const [width, setWidth] = useState(window.innerWidth);
91+
useEffect(() => {
92+
const onResize = () => setWidth(window.innerWidth);
93+
window.addEventListener('resize', onResize);
94+
return () => window.removeEventListener('resize', onResize);
95+
}, []);
96+
return width;
97+
}
98+
99+
function Header() {
100+
const width = useWindowWidth();
101+
console.log('Header render');
102+
return <h1>Window: {width}px</h1>;
103+
}
104+
```
105+
106+
Every time you resize your browser window, even by just 1 pixel, the `Header` component re-renders! If you use this hook in multiple components, you could end up with a lot of unnecessary re-renders.
107+
108+
To avoid this:
109+
110+
- Use throttling or debouncing for frequent updates
111+
- Only use hooks in components that really need them
112+
113+
Here's how to implement throttling to prevent excessive re-renders:
114+
115+
```jsx
116+
import {useState, useEffect, useRef, useCallback} from 'react';
117+
import {throttle} from 'lodash';
118+
119+
function useThrottledWindowWidth(delay = 100) {
120+
// 1) Initialize state safely (SSR-friendly)
121+
const [width, setWidth] = useState(() =>
122+
typeof window !== 'undefined' ? window.innerWidth : 0
123+
);
124+
125+
// 2) Create a stable, memoized throttled handler
126+
const throttled = useRef(
127+
throttle(() => {
128+
setWidth(window.innerWidth);
129+
}, delay)
130+
);
131+
132+
// 3) Whenever `delay` changes, re-create the throttle function
133+
useEffect(() => {
134+
throttled.current = throttle(() => setWidth(window.innerWidth), delay);
135+
return () => throttled.current.cancel();
136+
}, [delay]);
137+
138+
// 4) Wire up the resize listener once
139+
useEffect(() => {
140+
if (typeof window === 'undefined') return;
141+
142+
const handler = () => throttled.current();
143+
window.addEventListener('resize', handler);
144+
return () => {
145+
window.removeEventListener('resize', handler);
146+
throttled.current.cancel();
147+
};
148+
}, []);
149+
150+
return width;
151+
}
152+
153+
// Usage remains the same:
154+
function Header() {
155+
const width = useThrottledWindowWidth(100);
156+
console.log('Header render');
157+
return <h1>Window: {width}px</h1>;
158+
}
159+
```
160+
161+
## The "Big Re-renders" Myth
162+
163+
Many developers worry about "big" components causing performance issues. But here's the truth: React's diffing algorithm is very efficient! Even lists with dozens of items render quickly.
164+
165+
{{< figure src="/images/2025/taming-react-re-renders/props-myth.jpg" caption="" >}}
166+
167+
This image indicates that when you pass an object as a prop using an inline object literal (e.g., `<Child value={{value}} />`), a new object is created on every render, even if its values haven't changed. React uses shallow comparison to check for prop changes. Since the object reference is different on each render, React thinks the prop has changed and re-renders the child component.
168+
169+
Instead of worrying about component size, focus on:
170+
171+
- Optimizing parent components
172+
- Stabilizing prop references
173+
- Using memoization when it makes sense
174+
175+
Here's how to use memoization:
176+
177+
```jsx
178+
const MemoizedList = React.memo(function ({items}) {
179+
console.log('List render');
180+
return null;
181+
});
182+
183+
// In parent:
184+
const stableItems = useMemo(() => items, [items]);
185+
<MemoizedList items={stableItems} />;
186+
```
187+
188+
## The solution: Moving State Down
189+
190+
One of the best ways to prevent unnecessary re-renders is to move state as close as possible to where it's used. Here's an example:
191+
192+
```jsx
193+
// Before: parent holds all item-states
194+
function Parent({initialItems}) {
195+
const [items, setItems] = useState(initialItems);
196+
return items.map((item, i) => (
197+
<Item
198+
key={i}
199+
item={item}
200+
onUpdate={(newItem) => {
201+
const copy = [...items];
202+
copy[i] = newItem;
203+
setItems(copy);
204+
}}
205+
/>
206+
));
207+
}
208+
209+
// After: each Item manages its own state
210+
function Parent({initialItems}) {
211+
return initialItems.map((item, i) => <Item key={i} initial={item} />);
212+
}
213+
214+
function Item({initial}) {
215+
const [item, setItem] = useState(initial);
216+
return (
217+
<div>
218+
<input
219+
value={item.text}
220+
onChange={(e) => setItem({...item, text: e.target.value})}
221+
/>
222+
</div>
223+
);
224+
}
225+
```
226+
227+
## Parting thoughts:
228+
229+
By moving state down to individual `Item` components, typing in one input only re-renders that specific item, not the entire list!
230+
231+
**Remember**: Optimizing React performance is more about understanding when and why components re-render than about complex optimizations. Start by profiling your app with [React DevTools](https://react.dev/learn/react-developer-tools), identify the bottlenecks, and apply these strategies where they make the most sense.
232+
233+
For even better debugging, check out [why-did-you-render](https://github.com/welldone-software/why-did-you-render) - a fantastic tool that logs when and why your components re-render, making it much easier to spot unnecessary re-renders in development.
234+
235+
Happy coding! πŸš€
54.7 KB
Loading
34.3 KB
Loading
42.9 KB
Loading
46.6 KB
Loading

0 commit comments

Comments
Β (0)