In the recent post about understanding how React renders components, we presented how React renders UI elements and also raised awareness of the fact that React renders everything when only one state or prop has been updated. That will get our apps slowly rendered if the components are complicated and take a few milliseconds to complete the rendering. In line with the series of rendering in React, we will discuss some techniques to speed up rendering.
Table of Contents
What is shallow comparison?
According to React’s official documentation, shallowCompare
performs a shallow equality check on the current props
and nextProps
objects as well as the current state
and nextState
objects. It does this by iterating on the keys of the objects being compared and returning true when the values of a key in each object are not strictly equal.
shallowCompare
returnstrue
if the shallow comparison for props or state fails and therefore the component should update.
shallowCompare
returnsfalse
if the shallow comparison for props and state both pass and therefore the component does not need to update.
Shallow comparison is a process in React to compare the current state or props of a component to the previous one to determine whether they are dirty or not. React performs a shallow comparison by comparing the reference values of the state or props objects. If the reference values are different, React assumes that the state or props have changed and triggers a re-render. However, if the reference values are the same, React assumes that the state or props have not changed and does not trigger a re-render.
As you have seen in part 1 of this series, the shallow comparison doesn’t check the attributes of states, therefore, React performs to render the entire component. In the next section, we will present some tips to improve performance.
The
useMemo
anduseCallback
Hooks are similar. The main difference is thatuseMemo
returns a memoized value anduseCallback
returns a memoized function.
Using useMemo
hook to return a memoized value
Getting back to the introductory project in part 1 of this series, we will create a new branch from the main one and name it caching
a branch. We suppose the sum
function to make an expensive computation so it should not be always called when rendering the SumComp
component. To avoid that issue, we wrap the logic of the sum
function into the userMemo
hook as in the following snippet.
const sum = useMemo(() => { return expensiveComputing(a, b); }, [a, b]);
Then, start the app and load the page on our web browser. Check the screenshot below for your reference.
Analysis of the log messages
When loading the page fully first, the expensive function is called once. Then, we clicked the Click here to change text and color
twice but the expensive function wasn’t invoked. React only called and rendered the corresponding elements, e.g., the colour and text of the first paragraph tag. Similarly, when clicking on any of the Increment
buttons, React also doesn’t call the function to random colour and text.
Using useCallback
hook to return a memoized function
If useMemo
hook returns a memoized value, useCallback
hook returns a memoized function. To help you ease the reading flow, let’s get back to the project as described in the previous section and work out the following example:
import React, {useState, useCallback} from 'react'; function ChildComponent({onClick}) { console.log('ChildComponent is rendered'); return ( <button onClick={onClick}>Click me</button> ); } function AnotherSumComp() { const [count, setCount] = useState(0); const [txt, setTxt] = useState("Some text…"); const incrementCount = useCallback(() => { console.log("Calling an expensive computing..."); setCount(prevCount => prevCount + 1); }, [setCount]); return ( <div> <p>Text: {txt}</p> <p>Count: {count}</p> <button onClick={() => { console.log("Setting a new text..."); const text = Math.random().toString(36).slice(2, 7); setTxt(text); }}>Set Text</button> {/*<button onClick={() => { setCount(count + 1); }}>Increment</button>*/} <button onClick={() => { incrementCount(); }}>Increment</button> <ChildComponent onClick={incrementCount}/> </div> ); } export default AnotherSumComp;
In the App
component, replace <SumComp/>
with <AnotherSumComp/>
. Start the app and open the console log of your browser. The output will look like in the following screenshot.
Analysis of the log messages
When the page is fully loaded, only one message is logged because it comes from the child component. The next step is to click on the buttons to see how React renders the components.
Click on the Set Text
button
Clicking on this button twice, React renders the entire component twice but only the text is updated.
Click on the button either Increment
or Click me
Clicking on these buttons, React also renders the entire component but only the child component is updated and the expensive function is called to compute the output.
Key Takeaways
In this part, we presented how to use useMemo
and useCallback
to avoid the issue of rendering everything in React. The useMemo
hook returns a memorized value while the useCallback
hook as its name returns a memorized function. For the sake of finality, feel free to clone and work out the source code published at https://github.com/ITersDesktop/react-caching in the caching
branch.
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
[4] React useMemo
Hook, https://www.w3schools.com/react/react_usememo.asp, accessed Jan 2nd 2014
[5] React useCallback
Hook, https://www.w3schools.com/react/react_usecallback.asp, accessed Jan 2nd 2024
[6] Awesome Javascript Interviews, https://github.com/rohan-paul/Awesome-JavaScript-Interviews/tree/master/React/Hooks, accessed Jan 2nd 2024