1. Memorization
useMemo()React.memouseCallback()
These ways reduce re-renderings by caching and returning the same result if the inputs are the same without any computations. When the inputs change, the cache gets invalidated and the new component state gets rendered.
useMemo()
React renders a lot. Whenever a state changes, the whole component re-renders. To understand how we can use the useMemo() hook, let's consider an example of an expensive function:
function App() {
const [text, setText] = useState("");
const expensiveFunction = (num) => {
console.log("Expensive function re-rendered");
for (let i = 1; i< 10000000; i++) {
num += i;
}
return num;
}
const sum = expensiveFunction(1000);
console.log("Component re-rendered");
return (
<div>
<input onChange={e => setText(e.target.value)} />
<h2>Total: {sum}</h2>
</div>
)
As you can see, there is an input field. And we use state to store the text. When we change the text input, it re-renders this App component. And when the component re-renders, it calls this function again and again. The both messages "Component re-rendered" and "Expensive function re-rendered" will be printed on the console for each render.
But it doesn't make sense to call this expensive function again, since the number never changes.
This is Why we should use the useMemo() hook. We can use useMemo() to memorize the sum.
const sum = useMemo(()=> expensiveFunction(5), [])
This way, for every change in text input, we only can see the message "Component re-rendered" on the console. Because the computed result is stored in the sum variable and useMemo() Hook will return it each time unless the inputs are changed.
React.memo
Right now we know how to memorize values, but what about components? So this time we wrapped the expensive function into a child component.
function App() {
const [text, setText] = useState("");
const sum = expensiveFunction();
console.log("Component re-rendered");
return (
<div>
<input onChange={e => setText(e.target.value)} />
<ExpensiveComponent/>
</div>
)
const ExpensiveComponent = () => {
console.log("Expensive function re-rendered");
let total = 0;
for (let i = 1; i< 10000000; i++) {
total += i;
}
return <div>ExpensiveComponent</div>;
};
React will re-render the child component if the parent re-renders. But sometimes it's unnecessary to re-render the child component. As you can see, when the text input changes, the App component (parent) re-renders and also the ExpensiveComponent component (child) re-renders.
So this is where React.memo comes in handy. We can wrap this ExpensiveComponent in the React.memo.
const ExpensiveComponent = Reac.memo(() => {
console.log("Expensive function re-rendered");
let total = 0;
for (let i = 1; i< 10000000; i++) {
total += i;
}
return <div>ExpensiveComponent</div>;
});
useCallback()
As discussed above, we already knew the useMemo() is for caching the result, React.memo is for caching the component. So now, we are gonna use useCallback() to cache the callback function.
For example, consider a component with a clickable item list.
function MyParent({ term }) {
const onClick = useCallback(event => {
console.log('Clicked Item : ', event.currentTarget);
}, [item]);
return (
<ListItem={item} onClick={onClick} />
);
}
In the above example, useCallBack() memoizes the onClick callback. So, it will not re-render the component if the user clicks the same item again and again.
Please be note that if the calculation is not expensive, most probably you don't need them. Overuse could cause some memory problems.
2. API Call Optimization
It’s common to use the useEffect() Hook for asynchronous data fetching operations in React applications. However, useEffect() runs and fetches data on each render, and in most situations, it keeps loading the same data.
As a solution, we can use the React Query library or SWR library to cache the response data as listed below. When we make an API call, useQuery or useSWR() will first return the data from the cache before continuing with the request. Then, it will retrieve the data from the server, and if there is no new data available, it will prevent the component from re-rendering.
useQuery()useSWR()
useQuery
First, you need to install this library.
npm i @tanstack/react-query
The useQuery hook accepts a queryKey and a queryFn as arguments, and returns an object with the data, loading state, and error state of the query.
import { useQuery } from '@tanstack/react-query';
function Users() {
const { data, error, isLoading } = useQuery(
'/api/users',
async () => {
const res = await fetch('/api/users');
return res.json();
}
);
if (isLoading) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;
return <div>{data.name}</div>;
}
Refer here for more info and examples: tanstack.com/query/v4/do…
useSWR
First, you need to install the library.
npm install swr
Here is a code snippet of how to use it.
import useSWR from 'swr';
function fetcher(url) {
return fetch(url).then(res => res.json());
}
function Users() {
const { data, error } = useSWR('/api/users', fetcher);
if (error) return <div>Failed to load data</div>;
if (!data) return <div>Loading...</div>;
return <div>{data.name}</div>;
}
Refer here for more info and examples: swr.vercel.app/
3. Using React Fragments
If you have worked with React before, you will know that React requires wrapping the components with a single parent element. Though it’s not directly about re-rendering, have you known that it affects the overall component rendering time?
As a solution, you can use React Fragments to wrap the components, and it will reduce the load on the DOM, resulting in faster rendering times and decreased memory usage.
const App = () => {
return (
<React.Fragment>
<p>Hello<p/>
<p>World<p/>
</React.Fragment>
);
};