One of the key features of React is its ability to update the user interface efficiently by re-rendering components when there are changes to the state or props. However, this re-rendering can be expensive, especially for complex applications with large amounts of data. This article will explore how React triggers a re-render.
Table of Contents
Revise the life cycle of a component in React
The life cycle of a React component represents the different phases of the lifetime of a component.
When does React render UI elements?
React is listening to any changes to states and props. Then, it will render or re-render UI elements if at least one change has been made to any of the UI elements. Suppose your component makes up more than one UI element. In that case, the rendering is potentially expensive because React will recompute everything although it uses shallow comparison to detect which elements have been updated in their states or props.
React recognised those changes because we use hooks such as useState
, useEffect
, and so on.
Let’s work out the following example:
import './App.css'; import SumComp from "./shared/SumComp"; function App() { return ( <div className="App"> <SumComp/> </div> ); } export default App;
The SumComp
component is defined below.
import React, {useState, useMemo} from 'react'; function SumComp() { console.log("Rendering SumComp component..."); const COLOURS = ['aqua', 'blue', 'fuchsia', 'gray', 'green', 'lime', 'maroon', 'navy', 'olive', 'orange', 'purple', 'red', 'silver', 'teal', 'yellow']; const cId = randomColor(COLOURS.length); const [colorName, setColorName] = useState(COLOURS[cId]); 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]); const sum = expensiveComputing(a, b); return ( <div> <p>Text: <span style={{"color": colorName}}>{txt}</span></p> <p>a: {a}</p> <p>b: {b}</p> <p>sum: {sum}</p> <button onClick={() => { const text = Math.random().toString(36).slice(2, 7); setTxt(text); const colorIndex = randomColor(COLOURS.length); const colorName = COLOURS[colorIndex]; setColorName(colorName); }}>Click here to change text and color</button> <button onClick={() => setA(a + 1)}>Increment a</button> <button onClick={() => setB(b + 1)}>Increment b</button> </div> ); } function randomColor(max) { console.log("Calling random color index...") return Math.floor(Math.random()*max); } function expensiveComputing(x, y) { console.log("Calling expensive computing..."); return x + y; } export default SumComp;
Starting the application, the page looks like below.
In the source code published here in the rendering
branch, we define a component called SumComp which has a paragraph to capture a random text, a paragraph to hold the value of the variable a
and another paragraph to hold the value of the variable b
. Also, we need another paragraph to hold the sum of the value a
and b
.
Analysis
After the page loading
As shown in the screenshot above, React has called the function to define SumComp
component (Rendering SumComp component) where the local function generates a colour code (Calling random color index) and the function computes a sum expensively (Calling expensive computing).
Click on the Click here to change text and color
button (called C1)
When clicking on the C1, React detects the changes to the colour name and text which are randomly chosen. It calls all functions defined in the component although the variable a
and b
do not change. The expensive computing function is also called even if a
and b
are still 0. Imagine that the expensive computing function takes longer to finish.
Click on the Increment a
or Increment b
button (called C2)
Similarly, clicking on any of these two buttons also hits all functions and logic defined in the SumComp
component. It shouldn’t behave that way. Certainly, React offers us some approaches to avoid such a problem, for instance, using useMemo
or useCallback
hook or Lazy loading
technique. We will learn about these techniques in the next posts.
Take away
In this post, we have learned how and when React renders and re-renders components based on the changes to the states or props in the components. When a state or prop is updated, React renders everything defined in components without taking care of whether the other states or props have been changed or not. That’s the reason we should pay attention to when building complex components where they have to cooperate with different states and data coming from different resources. To grasp more about optimizing rendering components, we have a series of improving React rendering for you. For the sake of finality, please take a look at our source code at https://github.com/ITersDesktop/react-caching.
References
[1] React, https://react.dev/, accessed Jan 2nd 2024
[2] Understanding React Re-rendering: An Overview of Shallow Comparison and Optimization with Examples, https://www.linkedin.com/pulse/understanding-react-re-rendering-overview-shallow-examples-pandey/, accessed Jan 2nd 2024
[3] Optimizing Web Apps in React, https://www.turing.com/kb/optimize-web-apps-in-react, accessed Jan 2nd 2024