-
Notifications
You must be signed in to change notification settings - Fork 6
Add blog post on optimizing React re-renders for better performance #265
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
05337f4
add blog post on optimizing React re-renders for better performance
Ajith-kumar-in b8f4a77
refactor: update TodoList component to use item IDs as keys and retur…
Ajith-kumar-in 19b5d49
publish: finalize and publish blog post on optimizing React re-render…
Ajith-kumar-in c1bf397
refactor: clean up whitespace in the throttling example and enhance c…
Ajith-kumar-in 74aca08
refactor: improve useThrottledWindowWidth hook for better SSR compati…
Ajith-kumar-in 06bec3a
Merge branch 'main' of https://github.com/incubyte/incubyte.github.io…
Ajith-kumar-in 2cd5985
refactor: update TodoApp to include unique IDs for todos and remove u…
Ajith-kumar-in 2a25a9b
feat: add author profile for Ajith Kumar and update blog post date wi…
Ajith-kumar-in 3b954d0
feat: add Ajith Kumar's profile image to author section
Ajith-kumar-in File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| --- | ||
| title: Ajith Kumar | ||
| image: '/authors/ajith-kumar/ajith-kumar.jpg' | ||
| subtitle: 'Software Craftsperson @Incubyte 💼 | Ruby on Rails Expert 💎 | React Enthusiast ⚛️ | Performance Optimizer 🚀 | Tech Writer ✍️' | ||
| --- | ||
|
|
||
| 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. | ||
|
|
||
| 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. 🌱🐠📺 | ||
|
|
||
| Connect with me on [LinkedIn](https://www.linkedin.com/in/ajithbuddy) or follow my coding journey on [GitHub](https://github.com/Ajith-kumar-in). |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,235 @@ | ||
| +++ | ||
| title = "Taming React Re-renders: A Guide to Optimizing Performance" | ||
| slug = "taming-react-re-renders" | ||
| date = 2025-10-05T20:48:34+05:30 | ||
| image = "/images/2025/taming-react-re-renders/header.jpg" | ||
| draft = false | ||
| authors = ["Ajith Kumar"] | ||
| description = "A comprehensive guide to understanding and optimizing React re-renders for better application performance" | ||
| tags = ["React", "Performance", "Software Craftsmanship"] | ||
| categories = ["React", "Performance", "Software Craftsmanship"] | ||
| type = "" | ||
| +++ | ||
|
|
||
| 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! | ||
|
|
||
| ## The Problem: Unnecessary Re-renders | ||
|
|
||
| Let's start with a simple example. Imagine you're building a todo list app: | ||
|
|
||
| ```jsx | ||
| function TodoApp() { | ||
| const [todos, setTodos] = useState([]); | ||
| const [text, setText] = useState(''); | ||
|
|
||
| const addTodo = () => { | ||
| const newTodo = { | ||
| id: Date.now(), | ||
| text: text | ||
| }; | ||
| setTodos([...todos, newTodo]); | ||
| setText(''); | ||
| }; | ||
|
|
||
| return ( | ||
| <div> | ||
| <input | ||
| value={text} | ||
| onChange={(e) => setText(e.target.value)} | ||
| placeholder='Add todo…' | ||
| /> | ||
| <button onClick={addTodo}>Add</button> | ||
| <TodoList items={todos} /> | ||
| </div> | ||
| ); | ||
| } | ||
|
|
||
| function TodoList({items}) { | ||
| return ( | ||
| <ul> | ||
| {items.map((item) => ( | ||
| <li key={item.id}>{item.text}</li> | ||
| ))} | ||
| </ul> | ||
| ); | ||
| } | ||
| ``` | ||
|
|
||
| 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. | ||
|
|
||
| ## Understanding React's Lifecycle | ||
|
|
||
| To fix unnecessary re-renders, we need to understand how React works. Think of React components like a tree: | ||
|
|
||
| - When a component first appears on screen, it **mounts** | ||
| - When its data (state or props) changes, it **updates** | ||
| - When it's removed from the screen, it **unmounts** | ||
|
|
||
| Here's what you need to know: | ||
|
|
||
| - **State changes** trigger re-renders | ||
|
|
||
| {{< figure src="/images/2025/taming-react-re-renders/state-changes-example.jpg" caption="" >}} | ||
2KAbhishek marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| 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. | ||
|
|
||
| - **Parent updates** cause child re-renders | ||
|
|
||
| {{< figure src="/images/2025/taming-react-re-renders/parent-example.jpg" caption="" >}} | ||
|
|
||
| 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. | ||
|
|
||
| - React is smart! It batches multiple state updates in event handlers into a single re-render | ||
|
|
||
| ## Watch Out for Custom Hooks! | ||
|
|
||
| Custom hooks are great for reusing logic, but they can cause performance issues behind your back. Here's an example: | ||
|
|
||
| ```jsx | ||
| function useWindowWidth() { | ||
| const [width, setWidth] = useState(window.innerWidth); | ||
| useEffect(() => { | ||
| const onResize = () => setWidth(window.innerWidth); | ||
| window.addEventListener('resize', onResize); | ||
| return () => window.removeEventListener('resize', onResize); | ||
| }, []); | ||
| return width; | ||
| } | ||
|
|
||
| function Header() { | ||
| const width = useWindowWidth(); | ||
| console.log('Header render'); | ||
| return <h1>Window: {width}px</h1>; | ||
| } | ||
| ``` | ||
|
|
||
| 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. | ||
|
|
||
| To avoid this: | ||
|
|
||
| - Use throttling or debouncing for frequent updates | ||
2KAbhishek marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| - Only use hooks in components that really need them | ||
|
|
||
| Here's how to implement throttling to prevent excessive re-renders: | ||
|
|
||
| ```jsx | ||
| import {useState, useEffect, useRef, useCallback} from 'react'; | ||
| import {throttle} from 'lodash'; | ||
|
|
||
| function useThrottledWindowWidth(delay = 100) { | ||
| // 1) Initialize state safely (SSR-friendly) | ||
| const [width, setWidth] = useState(() => | ||
| typeof window !== 'undefined' ? window.innerWidth : 0 | ||
| ); | ||
|
|
||
| // 2) Create a stable, memoized throttled handler | ||
| const throttled = useRef( | ||
| throttle(() => { | ||
| setWidth(window.innerWidth); | ||
| }, delay) | ||
| ); | ||
|
|
||
| // 3) Whenever `delay` changes, re-create the throttle function | ||
| useEffect(() => { | ||
| throttled.current = throttle(() => setWidth(window.innerWidth), delay); | ||
| return () => throttled.current.cancel(); | ||
| }, [delay]); | ||
|
|
||
| // 4) Wire up the resize listener once | ||
| useEffect(() => { | ||
| if (typeof window === 'undefined') return; | ||
|
|
||
| const handler = () => throttled.current(); | ||
| window.addEventListener('resize', handler); | ||
| return () => { | ||
| window.removeEventListener('resize', handler); | ||
| throttled.current.cancel(); | ||
| }; | ||
| }, []); | ||
|
|
||
| return width; | ||
| } | ||
|
|
||
| // Usage remains the same: | ||
| function Header() { | ||
| const width = useThrottledWindowWidth(100); | ||
| console.log('Header render'); | ||
| return <h1>Window: {width}px</h1>; | ||
| } | ||
| ``` | ||
|
|
||
| ## The "Big Re-renders" Myth | ||
|
|
||
| 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. | ||
|
|
||
| {{< figure src="/images/2025/taming-react-re-renders/props-myth.jpg" caption="" >}} | ||
|
|
||
| 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. | ||
|
|
||
| Instead of worrying about component size, focus on: | ||
|
|
||
| - Optimizing parent components | ||
| - Stabilizing prop references | ||
| - Using memoization when it makes sense | ||
|
|
||
| Here's how to use memoization: | ||
|
|
||
| ```jsx | ||
| const MemoizedList = React.memo(function ({items}) { | ||
| console.log('List render'); | ||
| return null; | ||
| }); | ||
|
|
||
| // In parent: | ||
| const stableItems = useMemo(() => items, [items]); | ||
| <MemoizedList items={stableItems} />; | ||
| ``` | ||
|
|
||
| ## The solution: Moving State Down | ||
|
|
||
| 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: | ||
|
|
||
| ```jsx | ||
| // Before: parent holds all item-states | ||
| function Parent({initialItems}) { | ||
| const [items, setItems] = useState(initialItems); | ||
| return items.map((item, i) => ( | ||
| <Item | ||
| key={i} | ||
| item={item} | ||
| onUpdate={(newItem) => { | ||
| const copy = [...items]; | ||
| copy[i] = newItem; | ||
| setItems(copy); | ||
| }} | ||
| /> | ||
| )); | ||
| } | ||
|
|
||
| // After: each Item manages its own state | ||
| function Parent({initialItems}) { | ||
| return initialItems.map((item, i) => <Item key={i} initial={item} />); | ||
| } | ||
|
|
||
| function Item({initial}) { | ||
| const [item, setItem] = useState(initial); | ||
| return ( | ||
| <div> | ||
| <input | ||
| value={item.text} | ||
| onChange={(e) => setItem({...item, text: e.target.value})} | ||
| /> | ||
| </div> | ||
| ); | ||
| } | ||
| ``` | ||
|
|
||
| ## Parting thoughts: | ||
|
|
||
| By moving state down to individual `Item` components, typing in one input only re-renders that specific item, not the entire list! | ||
|
|
||
2KAbhishek marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| **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. | ||
|
|
||
| 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. | ||
|
|
||
| Happy coding! 🚀 | ||
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.