Implementing Robust Error Handling with Performance Optimization (memo, useCallback, useMemo) in React

As React developers, implementing robust error handling is crucial for a seamless user experience. However, excessive re-rendering of components can significantly affect the performance of a React application, especially when dealing with complex state updates. In this post, we'll explore common misconceptions about performance optimization in React using memo
, useCallback
, and useMemo
and provide practical examples and corrected code snippets.
Misconception 1: Overusing useCallback
for State Updates
Example code:
import React, { useState } from 'react';
function MyComponent() {
const [count, setCount] = useState(0);
const seldomComponent = React.useCallback((e) => {
console.log(`Received props: ${e}`);
}, [count]);
const handleButtonClick = () => {
setCount(count + 1);
seldomComponent()
};
return (
<div>
<p>Click count: {count}</p>
<button onClick={handleButtonClick}>Increment</button>
</div>
);
}
In this example, every component re-render occurs, even though seldomComponent
doesn't need to change. Fixing this, we use useCallback
's dependency array to only create a new seldomComponent
function if and only if the count
state changes.
Corrected Code:
import React, { useState } from 'react';
function MyComponent() {
const [count, setCount] = useState(0);
const seldomComponent = React.useCallback((e) => {
console.log(`Received props: ${e}`);
}, []);
const handleButtonClick = () => {
setCount(count + 1);
};
return (
<div>
<p>Click count: {count}</p>
<button onClick={handleButtonClick}>Increment</button>
</div>
);
}
Misconception 2: Misusing useMemo
Example code:
import React from 'react';
function MyComponent() {
const result = useMemo(
() => {
let isRecipeLoaded = false;
const promises = [
new Promise((resolve, reject) => {
// awaiting for some api
setTimeout(() => {
resolve();
}, 1000);
}),
new Promise((resolve, reject) => {
setTimeout(() => {
isRecipeLoaded = true;
resolve();
}, 2000);
}),
];
return result;
},
[] // WRONG! HERE
);
return (
<div>
{isRecipeLoaded && <button></button>}
</div>
);
}
Here, isRecipeLoaded
is not properly memoized. The array passed to useMemo
is empty, meaning it will only run once as soon as the component is initialized. Let's fix this.
Corrected Code:
import React from 'react';
function MyComponent() {
const isRecipeLoaded = React.useMemo(() => {
let result = false;
const promises = [
new Promise((resolve, reject) => {
// awaiting for some api
setTimeout(() => {
result = true;
resolve();
}, 1000);
}),
new Promise((resolve, reject) => {
setTimeout(() => {
result = true;
}, 2000);
}),
];
result;
}, []); // CORRECT!
return (
<div>
{isRecipeLoaded && <button></button>}
</div>
);
}
In the corrected version, useMemo
now properly depends only on the ref value it needs, namely to indicate if it has loaded. The memoization will be then triggered whenever useMemo
runs and not more often (avoiding performance degradation).
Misconception 3: Overusing memo
for Trivial Components
Here's a minor code snippet where the initial render of the Avatar
doesn't utilize the computed idDataAvatars.userInfo.currentAvatarPorts
unless you update within the latter group.
function Avatar(props) {
const idDataAvatars = useMemo(() => {
// complicated computation to extract avatar via filtering
}, [props.user.userInfo.currentAvatarPorts]);
const [likes, setLikes] = useState(() => 1);
return (
<div>
<img src={idDataAvatars}></img>
<button Liked by {likes} users, like this! onClick={() => setLikes(likes + 1)}>Like</button>
</div>
);
}
In the initial render, it is unnecessary to do anything even more computational, namely we simply use the state to update, yet unnecessarily caluclate idDataAvatars
.
Here's the corrected code.
Corrected Code:
import React from 'react';
function Avatar(props) {
const idDataAvatars = React.useMemo(() => {
// complicated computation to extract avatar via filtering
}, [props.userInfo.currentAvatarPorts]);
const [likes, setLikes] = useState(() => 1);
return (
<div>
<img src={idDataAvatars}></img>
<button Liked by {likes} users, like this! onClick={() => setLikes(likes + 1)}>Like</button>
</div>
);
}
In this corrected code, we memoize idDataAvatars
via the computed props.userInfo.currentAvatarPorts
, and delegate the state change to likes
respectively. This fundamental combination exploits the React lifecycle to ensure both sides' one-offs are triggered once.
Subscribe to my newsletter
Read articles from Sabin Chapagain directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
