Using React.memo for Controlling Component Rendering
16 August 2022
React Top Level API
The React library contains some functions at it's top level scope. Amongst these are the built-in hooks (like useState
, useCallback
, etc.) as well as some other functions for manipulating React Elements directly - which I've covered in a previous post on The React Top Level API
Component Rendering
By default, React will trigger a component render whenever there is a change to its state
or props
. React.memo
allows us to take control of the props
triggered render by giving us a way to look into the prop-change process
React.memo
is a higher order component (HOC) that allows us to wrap a component and control whether or not it is updated/re-rendered by defining a function that tells react whether or not it's props are different - and effectively whether this should trigger a new render
Doing the above is useful for complex components that don't necessarily need to be rendered every time their props are changed
API Definition
The React Docs give us the following example for the React.memo
HOC:
const MyComponent = (props) => {
/* render using props */
}
const areEqual = (prevProps, nextProps) => {
/*
return true if passing nextProps to render would return
the same result as passing prevProps to render,
otherwise return false
*/
}
const MyComponentMemo = React.memo(MyComponent, areEqual);
The component MyComponent
will be rendered whenever props are changed, however, using React.memo
lets us define a function called areEqual
that we can can use to tell React.memo
whether or not the new props would render a different result to the old props
We can then use MyComponentMemo
in place of MyComponent
to take control of when the component is rendered
Rendering On a Specific Type of Change
Say we have the specific component TimeDisplay
which shows the time that's being passed into it from App
:
import './App.css'
import React, { useState, useEffect } from 'react'
interface TimeDisplayProps {
time: number
}
const TimeDisplay: React.FC<TimeDisplayProps> = ({ time }) => {
const display = new Date(time).toString()
return <h1>{display}</h1>
}
export default function App() {
const [time, setTime] = useState(Date.now())
useEffect(() => {
const handle = setInterval(() => {
setTime(Date.now())
}, 100)
return () => {
clearInterval(handle)
}
}, [])
return (
<main>
<TimeDisplay time={time} />
</main>
)
}
The TimeDisplay
component in our case only displays time to the second, so any millisecond-level changes don't matter to the component and so we can save on those renders by checking if the difference in time
is similar to the previous render's time
Let's assume for our purpose that it's acceptable for the time to be delayed by about 5 seconds, we then can define a function called areTimesWithinOneSecond
which compares the next render's props with the previous and returns if they are within 5 seconds of each other:
const areTimesWithinFiveSeconds = (prev: TimeDisplayProps, next: TimeDisplayProps): boolean => {
const diff = next.time - prev.time
return diff < 5000
}
We can use the above function in a React.memo
to define a version of the TimeDisplay
component which will prevent unnecessary renders:
const TimeDisplayMemo = React.memo(TimeDisplay, areTimesWithinFiveSeconds)
And it can then be used as a drop-in replacement for the TimeDisplay
component:
export default function App() {
const [time, setTime] = useState(Date.now())
useEffect(() => {
const handle = setInterval(() => {
setTime(Date.now())
}, 100)
return () => {
clearInterval(handle)
}
}, [])
return (
<main>
<TimeDisplayMemo time={time} />
</main>
)
}
Conclusion
From the above implementation we can see that it's possible to delay rendering of a component using React.memo
if the component doesn't need to be re-rendered, hence improving performance by decreasing the number of renders react needs to carry out
REPL
The REPL with the example above can seen below: