React is all about building fast and dynamic user interfaces. However, as your application grows, performance bottlenecks can creep in, often caused by unnecessary re-renders. Thankfully, React provides tools like React.memo, useMemo, and useCallback to help optimize your app. I was recently working on building a React component for Autonmis and the component was rerendering infinitely. I was able to solve the issue using Memoization and that’s the reason I decided to write this article :)
In this article, we’ll explore these techniques with real-world examples to understand their differences and applications.
React.memo: Memoizing Components
What it does: React.memo
is a higher-order component (HOC) that memoizes a functional component. This means React skips re-rendering the component if its props haven’t changed.
Use Case: When you have a pure functional component that relies solely on its props and doesn’t need to re-render unless those props change.
Example: Preventing Unnecessary Re-Renders
import React from "react";
const ChildComponent = React.memo(({ label, onClick }) => {
console.log("ChildComponent rendered");
return <button onClick={onClick}>{label}</button>;
});
export default function App() {
const [count, setCount] = React.useState(0);
const increment = () => setCount((prev) => prev + 1);
return (
<div>
<h1>Count: {count}</h1>
<button onClick={increment}>Increment</button>
{/* React.memo prevents re-rendering unless 'label' or 'onClick' changes */}
<ChildComponent label="Click Me" onClick={() => alert("Hello!")} />
</div>
);
}
Output: When you click the "Increment" button, the ChildComponent
won’t re-render because its props haven’t changed. Without React.memo
, it would re-render unnecessarily.
useMemo: Memoizing Computations
What it does: useMemo
memoizes the result of a computation so that it only recomputes when its dependencies change. It’s perfect for expensive calculations or derived state that don’t need to be recalculated on every render.
Example: Optimizing Expensive Calculations
import React, { useState, useMemo } from "react";
export default function App() {
const [count, setCount] = useState(0);
const [text, setText] = useState("");
// Expensive computation
const expensiveCalculation = useMemo(() => {
console.log("Performing expensive calculation...");
return count * 2;
}, [count]);
return (
<div>
<h1>Count: {count}</h1>
<h2>Result: {expensiveCalculation}</h2>
<button onClick={() => setCount((prev) => prev + 1)}>Increment</button>
<input
type="text"
value={text}
onChange={(e) => setText(e.target.value)}
placeholder="Type something..."
/>
</div>
);
}
Output: The expensive calculation runs only when count
changes, not when typing in the input field. Without useMemo
, the calculation would run on every render, even if unrelated state changes.
useCallback: Memoizing Functions
What it does: useCallback
memoizes a function to ensure that it maintains the same reference across renders. This is especially useful when passing functions as props to memoized components.
Use Case: Prevent child components from re-rendering when the parent re-renders, by ensuring function props don’t change unnecessarily.
Example: Passing Stable Function References
import React, { useState, useCallback } from "react";
const ChildComponent = React.memo(({ onClick }) => {
console.log("ChildComponent rendered");
return <button onClick={onClick}>Click Me</button>;
});
export default function App() {
const [count, setCount] = useState(0);
// Memoize the callback to prevent ChildComponent from re-rendering
const handleClick = useCallback(() => {
alert("Button clicked!");
}, []);
return (
<div>
<h1>Count: {count}</h1>
<button onClick={() => setCount((prev) => prev + 1)}>Increment</button>
{/* Pass the memoized callback */}
<ChildComponent onClick={handleClick} />
</div>
);
}
Output: The ChildComponent
only re-renders when its props change. Without useCallback
, the handleClick
function would be recreated on every render, causing the child to re-render unnecessarily.
Key Differences
Here’s a quick comparison to help you choose the right tool:
Feature | React.memo | useMemo | useCallback |
Purpose | Memoizes components | Memoizes values | Memoizes functions |
Usage | For optimizing component rendering | For expensive calculations | For functions passed as props |
Trigger | Props comparison | Dependency array changes | Dependency array changes |
Pro Tips for Using Memoization
Don’t Overuse It: These tools add complexity to your code. Use them only when profiling reveals performance bottlenecks.
Combine React.memo and useCallback: Use
React.memo
to optimize child components anduseCallback
to prevent passing new function references unnecessarily.Know When Not to Memoize: If your components or computations are lightweight, memoization might not provide significant benefits and can make debugging harder.
Interested in diving deeper into React and modern web development? Let us know your favorite topics, and we'll bring you more insightful content to level up your skills!
Feel free to join the DevHub community for all things Development :)