When you code in React, you want your app to run fast and smooth. One way to do that is to use caching, which means storing some data or functions in memory so they don't have to be recalculated or recreated every time.
Caching in React is also called memoization. It helps you avoid unnecessary re-rendering of components when their state or props change.
React gives you two hooks for caching: useMemo and useCallback. useCallback lets you cache a function, while useMemo lets you cache a value. You can also use these hooks with the Context API to make your app even more efficient.
Here’s a basic list of topics we’ll be covering in this article:
- React caching default behavior.
- The useMemo hook.
- The useCallback hook.
In order to follow along, you'll need a decent understanding of React and stateful components.
Default Caching Behavior in React
By default, React uses a technique called “shallow comparison” to determine whether a component should be re-rendered. This basically means that if the props or state of a component haven’t changed, React will assume that the output of the component hasn’t changed either and won’t re-render it.
While this default caching behavior is very effective by itself, it isn’t always enough to optimize complex components that require advanced state management.
In order to achieve more control over your component’s caching and rendering behavior, React offers the useMemo and useCallback hooks.
Caching in React with the useMemo Hook
useMemo is useful when you need to do an expensive computation to retrieve a value, and you want to ensure that the computation is only performed when necessary. By memoizing the value using useMemo, you can ensure that the value is only computed when its dependencies change.
In a React component, you may have multiple properties that make up your state. If a piece of state changes that has nothing to do with our expensive value, why recompute it if it hasn’t changed?
Here’s an example code block reflecting a basic useMemo implementation:
react
import React, { useState, useMemo } from 'react';
function Example() {
const [txt, setTxt] = useState(“Some text”);
const [a, setA] = useState(0);
const [b, setB] = useState(0);
const sum = useMemo(() => {
console.log('Computing sum...');
return a + b;
}, [a, b]);
return (
<div>
<p>Text: {txt}</p>
<p>a: {a}</p>
<p>b: {b}</p>
<p>sum: {sum}</p>
<button onClick={() => setTxt(“New Text!”)}>Set Text</button>
<button onClick={() => setA(a + 1)}>Increment a</button>
<button onClick={() => setB(b + 1)}>Increment b</button>
</div>
);
}
In our Example component above, assume the sum() function performs an expensive computation. If the txt state is updated, React is going to re-render our component, but because we memoized the returned value of sum, this function will not run again at this time.
The only time the sum() function will run is if either the a or b state has been mutated (changed). This is an excellent improvement upon the default behavior, which will rerun this method upon each re-render.
Caching in React with the useCallback Hook
useCallback is useful when you need to pass a function as a prop to a child component, and you want to ensure that the function reference does not change unnecessarily. By memoizing the function using useCallback, you can ensure that the function reference remains the same as long as its dependencies do not change.
Without getting too heavy into JavaScript function references, let’s just take a look at how they can affect the rendering of your React app. When a function reference changes, any child components that receive the function as a prop will re-render, even if the function logic itself has not changed.
This is because, as we already mentioned, React does a shallow comparison of prop values to determine whether a component should re-render, and a new function reference will always be considered a different value than the previous one.
In other words, the simple act of redeclaring a function (even the same exact function), causes the reference to change, and will cause the child component that receives the function as a prop to unnecessarily re-render.
Here’s an example code block reflecting a basic useCallback implementation:
react
import React, { useState, useCallback } from 'react';
function ChildComponent({ onClick }) {
console.log('ChildComponent is rendered');
return (
<button onClick={onClick}>Click me</button>
);
}
function Example() {
const [count, setCount] = useState(0);
const [txt, setTxt] = useState(“Some text…”);
const incrementCount = useCallback(() => {
setCount(prevCount => prevCount + 1);
}, [setCount]);
return (
<div>
<p>Text: {txt}</p>
<p>Count: {count}</p>
<button onClick={setTxt}>Set Text</button>
<button onClick={setCount}>Increment</button>
<ChildComponent onClick={incrementCount} />
</div>
);
}
As you can see in the above example, we pass the incrementCount method instead of the setCount method to the child component. This is because incrementCount is memoized, and when we run our setTxt method, it won’t cause the child component to unnecessarily re-render.
The only way our child component will re-render in this example is if the setCount method runs, because we passed it as a dependency parameter to our useCallback memoization.
Conclusion
Caching is a technique that helps your React app run faster and smoother. It does this by avoiding unnecessary re-renders of components when their state or props change.
React already does some caching for you by using a virtual DOM to compare changes and update components only when needed. This works well in most cases, but sometimes you might want more control over when and how components re-render.
That's where useMemo and useCallback hooks come in handy.
useMemo lets you cache the result of a function that is costly to run and doesn't change often.
useCallback lets you cache the reference of a function that is passed as a prop to child components that might re-render too often.
No comments:
Post a Comment