Introduction
To memoize is to ask the function to remember the result without needing to recalculate the result. In this article, we will explore Memoization Methods in React JS. Memoization is typically applied to functions that demand intensive processing. We essentially trade our memory with speed to increase performance by saving the previous results.
What is Memoization?
Memoization is a form of caching that speeds up our programs and holds some data in an accessible box for later use. It stores and uses the already processed results when a similar query is called again. It is an optimization technique used in computing. Now caching is useful in React, especially while the components are re-rendered.
Memoization in React
React has four APIs for memoization: PureComponent , memo, useMemo, and useCallback. PureComponent and memo hook are the features to implement memoization in React.PureComponent depends on the shouldComponentUpdate() lifecycle method; however, it is only used with the class component. The memo()
hook allows us to perform optimization for functional components. If a component gives the same result for the same prop, we can use memoization and skip the re-rendering using the previously saved result.
[Also Read, React JS Tutorial for Beginners]
Using React.PureComponent
React.PureComponent resembles React.Component. The dissimilarity between them is that React.PureComponent is only confined to class components that rely on the shouldComponentUpdate() lifecycle method and state. React.PureComponent performs optimization using componentShouldUpdate() lifecycle method that compares props and state from the previous render of the component. Because shallow rendering only tests the state and props of the component and the child components with React.PureComponent.
While operating React.PureComponent all child components should also be pure.
Following are the circumstances under which you can use React.PureComponent
- State/Props should be an immutable object
- State/Props should not have a hierarchy
- We should call forceUpdate when data changes
Consider the following Example:
//App.js
import React, { Component } from 'react'
import './App.css'
import ParentComp from './ParentComp'
class App extends Component {
render() {
return (
<div className='App'>
<ParentComp/>
</div>
)
}
}
export default App
//ParentComp.js
import React, { Component } from 'react'
import RegularComp from './RegularComp'
import PureComp from './PureComp'
class ParentComp extends Component {
constructor(props) {
super(props)
this.state = {
name:'Justin'
}
}
componentDidMount()
{
setInterval(()=>{
this.setState({
name:'Justin'
})
},2000)
}
render() {
console.log('--------Parent Component rendered-------')
return (
<div>
ParentComp
<RegularComp name={this.state.name}/>
<PureComp name={this.state.name}/>
</div>
)
}
}
export default ParentComp
//PureComp.js
import React, { PureComponent } from 'react'
class PureComp extends PureComponent {
render() {
console.log('Pure Component rendered')
return (
<div>Hello {this.props.name} This is Pure Component</div>
)
}
}
export default PureComp
// RegularComp.js
import React, { Component } from 'react'
class RegularComp extends Component {
render() {
console.log('Regular Component rendered')
return (
<div>Hello {this.props.name} This is Regular Component</div>
)
}
}
export default RegularComp
Here we have a parent component with two child components, a regular class component and a Pure Component. The parent component re-renders every 2 seconds and has a name as a property that can be used as a prop. Every time the parent component re-renders, the name is set to the same default.
Looking at the output, we can see that the Pure component is rendered just once, unlike the regular component, which renders after every 2 seconds. This is because a regular component does not implement shouldComponentupdate() method. It always returns true by default, but a pure component does. It implements the method with shallow prop and state comparison. Here the property name never changes, so the method returns false, and the Pure component is not rendered until it does.
Using React.memo
React.memo() is a higher-order component (HOC). HOC functions take a component as input and return a new component. It works exactly like the PureComponent() but for the functional component and not the class component. Using React.memo() we can implement memoization in the functional components. It renders the component only when the props are changed. Hence we wrap our component in the memo.
We can also pass a second argument in the memo function. It can be our comparison function. It specifies whether component rendering is needed or not.
Consider the Example we used for PureComponent(). Let’s also create a functional component inside the parent component.
import React, { Component } from 'react'
import RegularFunComp from './RegularFunComp'
import MemoComp from './MemoComp'
class ParentComp extends Component {
constructor(props) {
super(props)
this.state = {
name:'Justin'
}
}
componentDidMount()
{
setInterval(()=>{
this.setState({
name:'Justin'
})
},2000)
}
render() {
console.log('--------Parent Component rendered-------')
return (
<div>
ParentComp
<RegularFunComp name={this.state.name}/>
<MemoComp name={this.state.name}/>
</div>
)
}
}
export default ParentComp
//MemoComp.js
import React from 'react'
function MemoComp({name}) {
console.log('Rendering Memo Component')
return (
<div> Hello {name} This is a Memo Component</div>
)
}
export default React.memo(MemoComp)
//RegularFunComp.js
import React from 'react'
function RegularFunComp({name}) {
console.log('Rendering Regular Functional Component')
return (
<div> Hello {name} This is a Regular Functional Component</div>
)
}
export default RegularFunComp
Here We have wrapped the MemoComp() function inside React.memo() unlike RegularFunComp().
We can see that the MemoComp() is rendered just once as there is no change in the props passed unlike the RegularFunComp() which is rendered after every two seconds.
The useMemo Hook
useMemo is one of the inbuilt React hooks. It takes two arguments: a function that computes the result and an array of dependencies. useMemo recomputes the memoized value only when one of the dependencies has changed. This bypasses expensive calculations on every render. The useMemo should only be used for performance optimization. Hence we should first write our code to work without useMemo and then add useMemo to optimize it. Also, the function passed to UseMemo runs during rendering, so make sure not to use anything like side effects that are not used during rendering.
const result = useMemo(() => doSomething(a, b), [a, b]);
Returning to our last Example, we can use the useMemo hook to render the first name from the child component. We must use the JSX returned from the child component inside the useMemo hook. The parent-call-to-child component can be modified to useMemo as below :
{useMemo(
() => (
<DisplayFirstName searchedFirstName={searchedFirstName} />
),
[searchedFirstName]
)}
Consider the following code :
import React, {useMemo, useState} from 'react'
export default function App() {
const[number,setNumber]= useState(0)
const[dark,setDark]= useState(false)
const doubleNumber = multiplyby2(number)
const theme= {
backgroundColor: dark ? 'black' : 'white',
color: dark? 'white' : 'black'
}
return(
<>
<input type="number" value={number} onChange={e => setNumber(parseInt(e.target.value))} />
<button onClick={()=> setDark(prevDark=>!prevDark)}> Change theme </button>
<div style={theme}>{doubleNumber}</div>
</>
)
}
function multiplyby2(num)
{
console.log('Doubling the number')
return num*2
}
Here we are printing double the number as well as we can change the theme. Now, the doubleNumber() function doesn’t need to be called; we change the theme, not the number. Still, “Doubling the number” is logged in the console whenever we change the number or toggle the theme. This is because our app function rerenders the doubleNumber function, even if the theme is changed.
We can avoid this by wrapping our function in React’s useMemo. useMemo stores the results of the previous operations and does not recreate the same code if the input is the same. In our case, the doubleNumber() function won’t be recreated if the input has appeared before or if there is no change in input altogether.
const doubleNumber = useMemo( ()=> {
return multiplyby2(number)
},[number])
The useCallback Hook
We can furthermore memoize callback functions using useCallback hook API. It is used to optimize the rendering nature of our React function components. It also takes an inline callback and an array as arguments, and returns memoized callback. If one of the dependencies in the array is altered, useCallback will return the memoized callback.
const result = useCallback(
() => {
doSomething(a, b);
},
[a, b],
);
For Example consider the code below:
//App.js
import React, {useCallback, useState} from 'react'
import List from './List.js';
export default function App() {
const[number,setNumber]= useState(1)
const[dark,setDark]= useState(false)
const getItems=() => {
return[number,number+10,number+20]
}
const theme= {
backgroundColor: dark ? '#333' : '#FFF',
color: dark? '#FFF' : '#333'
}
return(
<div style={theme}>
<input type="number" value={number} onChange={e => setNumber(parseInt(e.target.value))} />
<button onClick={() => setDark(prevDark => !prevDark)}>Change theme</button>
<List getItems={getItems} />
</div>
)
}
//List.js
import React, {useEffect,useState} from 'react'
export default function List ({getItems}){
const[items,setItems]= useState([])
useEffect(()=>{
setItems(getItems())
console.log('Items Updated')
}, [getItems])
return items.map(item=> <div key={item}>{item}</div>)
}
In the above code, if we input a number, its three successors get displayed as well as we have a theme-changing button. Every time we input a new number, our getItems() function gets updated, and “Updating Items” is logged in the console. Now the concern here is that the getItems() function is not only updated by changing the input but also by changing the overhead theme. This happens because the getItems() function in our app component is being recreated every single time we render our app component, even if the actual number is not changed.
Now, this is where we can use useCallback(). We wrap our getItems function inside the useCallback() functions with the array of dependencies. Here the only thing our function depends on is the input number. Now the getItems function is recreated only when the number variable changes and not when we toggle the theme.
//App.js
import React, {useCallback, useState} from 'react'
import List from './List.js';
export default function App() {
const[number,setNumber]= useState(1)
const[dark,setDark]= useState(false)
const getItems = useCallback(() => {
return [number, number + 10, number + 20];
}, [number]);
const theme= {
backgroundColor: dark ? '#333' : '#FFF',
color: dark? '#FFF' : '#333'
}
return(
<div style={theme}>
<input type="number" value={number} onChange={e => setNumber(parseInt(e.target.value))} />
<button onClick={() => setDark(prevDark => !prevDark)}>Change theme</button>
<List getItems={getItems} />
</div>
)
}
Conclusion
- PureComponent is used for the class components.
- React Function components are concealed in React.memo to prevent re-renderings.
- Use the useMemo hook when you want to remember an excessive value returned by a function, like an HTTP request that gets tons of data.
- useCallback hook is used when you want to pass callbacks to child components through props.
Thanks for reading Memoization Methods in React JS. Hope you found this article helpful.
2 comments
Comments are closed.