Optimizing React Performance: Preventing Unnecessary Child Component Re-renders
The Problem: To understand the issue better, let’s start with a simple React component structure consisting of a parent component (App
) and a child component (Child
). The parent manages a state variable, and we want to avoid the child component re-rendering when the parent's state changes.
import React, { useState } from 'react';
function App() {
const [count, setCount] = useState(0);
return (
<>
<h1>Hello</h1>
<Child />
<button onClick={() => setCount(count + 1)}>Click Me!</button>
</>
);
}
const Child = () => {
console.log("==Child rendering====");
return (
<button>
<h3>{'A'}</h3>
</button>
);
}
In this scenario, every time the parent’s state changes, the Child
component re-renders unnecessarily, even though its props remains the same.
The Solution: Memoization with React.memo
To solve this problem, we can use React.memo
to memoize the Child
component. This means the child will only re-render when its props change, effectively preventing unnecessary renders.
import React, { useState } from 'react';
function App() {
console.log("==Parent rendering====");
const [count, setCount] = useState(0);
return (
<>
<h1>Hello</h1>
<Child />
<button onClick={() => setCount(count + 1)}>Click Me!</button>
</>
);
}
const Child = React.memo(() => {
console.log("==Child rendering====");
return (
<button>
<h3>{'A'}</h3>
</button>
);
});
Additional Challenges: What if we need to pass Function/Objects/Maps as props.
import React,{ useState } from 'react'
import './App.css'
function App() {
console.log("==Parent rendering====");
const [count, setCount] = useState(0)
const handleClick = () =>{
//...
}
return (
<>
<h1>Hello</h1>
<Child handleClick={handleClick} />
<button onClick={() =>setCount(count+1)}>Click Me!</button>
</>
)
}
const Child = React.memo(({handleClick}) => {
console.log("==Child rendering====");
return (
<button>
<h3>{'A'}</h3>
</button>
);
})
export default App
Here, we are passing a new inline function (handleClick
) as a prop to the Child
component every time the parent component re-renders. Even though the Child
component is memoized, the new handleClick
function is a different reference on every render. This causes the Child
component to re-render, as it sees a new prop reference, even though the function itself has not changed.
Using useCallback:
import React,{ useCallback, useState } from 'react'
import './App.css'
function App() {
console.log("==Parent rendering====");
const [count, setCount] = useState(0)
const handleClick = useCallback(() =>{
//...
},[])
return (
<>
<h1>Hello</h1>
<Child handleClick={handleClick} />
<button onClick={() =>setCount(count+1)}>Click Me!</button>
</>
)
}
const Child = React.memo(({handleClick}) => {
console.log("==Child rendering====");
return (
<button>
<h3>{'A'}</h3>
</button>
);
})
export default App
useCallback
is a hook in React that memoizes functions. It's useful when you want to prevent the recreation of a function on every render, especially when that function is used as a prop for child components.
Here, handleClick
is memoized using useCallback
. The second argument ([]
) is an empty dependency array, meaning the function is memoized once during component initialization and will always have the same reference across re-renders. This prevents unnecessary re-creation of the handleClick
function on each render, optimizing performance.
Using useMemo:
import React, { useState, useMemo } from 'react';
function App() {
console.log("==Parent rendering====");
const [count, setCount] = useState(0);
// Compute a value memoized with useMemo
const computedValue = useMemo(() => {
//Some Expensive computation
return count * 2;
}, [count]);
return (
<>
<h1>Hello</h1>
<Child value={computedValue} />
<button onClick={() => setCount(count + 1)}>Click Me!</button>
</>
);
}
const Child = React.memo(({value}) => {
console.log("==Child rendering====");
return (
<button>
<h3>{'A'}</h3>
</button>
);
})
export default App;
useMemo
is another hook in React, but it's used to memoize values, not functions. It's handy when you want to avoid recomputing a value on every render, especially when that computation is expensive.
In this example, the computedValue
is calculated using useMemo
. It depends on the count
state variable, so it will be recomputed only when count
changes. The expensive computation is performed only when necessary, helping to optimize performance by avoiding needless recalculations.
In summary, useCallback
is used to memoize functions, preventing unnecessary function recreations, while useMemo
is used to memoize values, optimizing performance by avoiding unnecessary recomputation. Both hooks are valuable tools for improving React application performance.
Note(🔥🔥🔥🔥🔥🔥🔥🔥🔥):
import React,{ useCallback, useState } from 'react'
import './App.css'
function App() {
console.log("==Parent rendering====");
const [count, setCount] = useState(0)
const handleClick = useCallback(() =>{
//...
},[])
return (
<>
<h1>Hello</h1>
<Child handleClick={() => handleClick()} />
<button onClick={() =>setCount(count+1)}>Click Me!</button>
</>
)
}
const Child = React.memo(() => {
console.log("==Child rendering====");
return (
<button>
<h3>{'A'}</h3>
</button>
);
})
export default App
In the provided code example, passing a function to a child component with an inline arrow function like this: <Child handleClick={() => handleClick} />
creates a new function reference on each parent component render, even though it essentially points to the same handleClick
function. This results in unnecessary re-renders of the child component, even when the underlying handleClick
function hasn't changed. To avoid these unnecessary re-renders and ensure optimal performance when using React.memo
, it's best to directly pass the handleClick
function as a prop, like this: <Child handleClick={handleClick} />
. This way, the child component accurately identifies that its props, including the handleClick
function, haven't changed, and it won't re-render needlessly. It's an important optimization to consider when working with React components to maintain a responsive and efficient application.