React is a powerful JavaScript library for building complex user interfaces. However, as applications grow in complexity, it's important to ensure that the performance of the application remains fast and responsive. In this chapter, we'll explore some tips and techniques for optimizing React performance.
Why components re-renders
React re-renders components for a variety of reasons, including:
Change in Props: If the props of a component change, React will re-render the component to reflect the new values.
Change in State: If the state of a component changes, React will re-render the component to reflect the new state.
Parent Component Re-renders: If a parent component re-renders, React may re-render its child components as well.
Context Changes: If a context value changes, React may re-render components that use that context.
While not all re-renders are bad, unnecessary re-renders can cause performance issues in our app.
React Profiler
Profiler is a built-in tool for measuring the performance of your React application. It allows you to track how long it takes to render each component, and identify components that are causing performance issues.
Some methods to increase performance are
Using React.memo
React.memo
is a higher-order component that you can use to memoize
the output of a function component. This means that React will only re-render the component if its props have changed, which can help to improve performance in certain cases.
const MyComponent = React.memo((props) => {
return (
<div>
{props.children}
</div>
);
});
export default MyComponent;
Alternatively, we can also write it as
const MyComponent = (props) => {
console.log('Rendering MyComponent');
return (
<div>
<h1>{props.title}</h1>
<p>{props.description}</p>
</div>
);
};
export default React.memo(MyComponent);
Using Keys
In React, when rendering a list of items, it is important to specify a unique key
prop for each item. This is because React uses the key
prop to identify which items have changed in the list and need to be updated, added or removed from the DOM.
When a list is rendered without keys, React may have to re-render all of the list items whenever a change is made to the list, even if only one item has been added or removed. This can be inefficient and cause unnecessary re-renders.
By adding a unique key
prop to each item in the list, React can identify which items have changed and only update those specific items in the DOM. This can significantly reduce the number of re-renders required and improve performance.
For example, consider a list of items that are rendered in a component:
function ItemList({ items }) {
return (
<ul>
{items.map(item => (
<li key={item.id}>{item.name}</li>
))}
</ul>
);
}
Here, we've added a key
prop to each li
element based on the id
property of the item. Now, when a change is made to the list, React will only update the specific items that have changed, rather than re-rendering the entire list. This can help improve performance and reduce unnecessary re-renders.
useMemo Hook
The useMemo
hook is another way to optimize performance in React by memoizing the result of a function call. It's similar to React.memo
, but instead of memoizing the entire component, it memoizes only the value that the function returns. This can be useful when you have a function that takes a long time to execute or when you want to avoid recalculating a value unnecessarily.
Here's an example of how to use the useMemo
hook:
import React, { useState, useMemo } from 'react';
function fibonacci(n) {
if (n <= 1) {
return 1;
}
return fibonacci(n - 1) + fibonacci(n - 2);
}
function App() {
const [number, setNumber] = useState(1);
const fib = useMemo(() => fibonacci(number), [number]);
return (
<div>
<h1>Fibonacci</h1>
<input type="number" value={number} onChange={(e) => setNumber(Number(e.target.value))} />
<p>{`Fibonacci(${number}) = ${fib}`}</p>
</div>
);
}
export default App;
In this example, we have a function called fibonacci
that calculates the nth Fibonacci number. We then use the useMemo
hook to memoize the result of calling fibonacci
with the current value of number
. We pass [number]
as the second argument to useMemo
, which tells React to only recalculate the value of fib
when number
changes.
When you run this example, you'll notice that the value of fib
is only calculated when number
changes. If you try entering a large value for number
, you'll see that the calculation takes some time, but subsequent changes to number
are much faster thanks to the memoization.
useCallback Hook
The useCallback
hook is another commonly used hook in React that helps optimize the performance of functional components. It is similar to the useMemo
hook in that it is used to memoize
functions, but instead of memoizing a value, it memoizes a function.
The useCallback
hook takes two arguments: the first is the function to memoize
, and the second is an array of dependencies
that should trigger a re-render of the memoized function if they change. The hook returns a memoized version of the function.
Here's an example of how to use the useCallback hook:
import React, { useState, useCallback } from 'react';
function fibonacci(n) {
if (n <= 1) {
return 1;
}
return fibonacci(n - 1) + fibonacci(n - 2);
}
function App() {
const [number, setNumber] = useState(1);
const fib = useCallback(() => fibonacci(number), [number]);
return (
<div>
<h1>Fibonacci</h1>
<input type="number" value={number} onChange={(e) => setNumber(Number(e.target.value))} />
<p>{`Fibonacci(${number}) = ${fib()}`}</p>
</div>
);
}
export default App;
In this example, we use the useCallback hook to memoize the fibonacci
function. We pass the number
state variable as a dependency to the useCallback hook, which ensures that the fibonacci
function is only re-created when the number
state variable changes.
We then call the memoized fib
function in the JSX using fib()
. This ensures that the fibonacci
function is only re-executed when the number
state variable changes, and not on every re-render of the component.
useTransition Hook
useTransition
is a React Hook that lets you update the state without blocking the UI.
We can use the useTransition
hook to tell React that a certain state change will result in an expensive rendering. React will then deprioritize this state change allowing other renderings to take place faster providing a very responsive UI. Such expensive renderings are called transition updates and the ones that should take place immediately are called urgent updates. Usually, typing, clicking, and pressing are considered urgent updates as they should provide users with an immediate response to ensure a good user experience.
Call useTransition
at the top level of your component to mark some state updates as non-blocking transitions
.
function TabContainer() {
const [isPending, startTransition] = useTransition();
const [tab, setTab] = useState('about');
function selectTab(nextTab) {
startTransition(() => {
setTab(nextTab);
});
}
// ...
}
Transitions let you keep the user interface updates responsive even on slow devices.
With a transition, your UI stays responsive in the middle of a re-render. For example, if the user clicks a tab but then changes their mind and clicks another tab, they can do that without waiting for the first re-render to finish.
useDeferredValue
This hook is very similar in functionality to the useTransition hook. While we use the useTransition hook to tell React that a certain setState method will trigger a transition update, we use the useDeferredValue hook to tell React that a received prop value will trigger a transition update.
Call useDeferredValue
at the top level of your component to defer updating some part of your UI.
import { useState, useDeferredValue } from 'react';function SearchPage() { const [query, setQuery] = useState(''); const deferredQuery = useDeferredValue(query); // ...}
During the initial render, the deferred value will be the same as the value you provided.
During updates, the deferred value will lag behind
the latest value. In particular, React will first re-render without updating the deferred value, and then try to re-render with the newly received value in the background.
Summary and further readings
There are several other ways to increase performance that will be discussed later. Some other useful resources are :
https://blog.logrocket.com/optimizing-performance-react-app/
https://www.codementor.io/blog/react-optimization-5wiwjnf9hj
https://www.bacancytechnology.com/blog/react-performance-optimization