Performance
Premature Optimization
Premature optimization is the root of all evil -- DonaldKnuth
Premature Optimization is optimizing before we know that we need to do it.
Recommendation: Get your application working and then near the end of a development cycle take the time to optimize for performance.
What causes a component to render
in React?
A re-render can only be triggered if a component’s state has changed. The state can change from a props
change, or from a call to setState
or a useState
update state function. The component gets the updated state and React decides if it should re-render the component. Unfortunately, by default React is incredibly simplistic and basically re-renders everything all the time.
Component changed? Re-render. Parent changed? Re-render parent and all it's children. Section of props that doesn't actually impact the view changed? Re-render.
Summary
Default Behavior: Changing state
- results in that component and all descendants being re-rendered.
Default Behavior: Update a prop in a component
- results in that component and all descendants re-rendered.
- the check for whether a prop changed uses a strict equality check
===
const a = { "test": 1 };
const b = { "test": 1'};
console.log(a === b); // will be false
const c = a; // "c" is just a reference to "a"
console.log(a === c); // will be true
Wasted Renders
React has two phrases that run sequentially to update the UI.
Render Phase
The "render phase" is where React compares a previous version of a Virtual DOM representing the UI with an updated version to figure out what if any changes need to be made.
Commit Phase
The "commit phase" is where React actually changes the real DOM.
As demonstrated in the Virtual DOM chapter React is very efficient about figuring out the minimal DOM operations to make in the "render phase" and batches them to make rendering the UI extremely performant.
However, the "render phase" does take work and consumes resources and should not take place if it isn't needed. If all the components on the screen are constantly rendering when the don't need to this is a common source of eventual performance problems. We call this problem: "wasted renders".
Wasted Renders can be fixed using:
React.Memo
when using function components.React.PureComponent
when using class components.
React.memo
const MyComponent = React.memo(function MyComponent(props) {
/* render using props */
});
React.memo
is a higher order component for function components and subsequently can only be used with function components.
If your function component renders the same result given the same props, you can wrap it in a call to React.memo
for a performance boost in some cases by memoizing the result. This means that React will skip rendering the component, and reuse the last rendered result.
React.memo
only checks for prop changes. If your function component wrapped inReact.memo
has auseState
oruseContext
Hook in its implementation, it will still rerender when state or context change.
By default it will only shallowly compare complex objects in the props object. If you want control over the comparison, you can also provide a custom comparison function as the second argument.
function MyComponent(props) {
/* render using props */
}
function areEqual(prevProps, nextProps) {
/*
return true if passing nextProps to render would return
the same result as passing prevProps to render,
otherwise return false
*/
}
export default React.memo(MyComponent, areEqual);
This method only exists as a performance optimization. Do not rely on it to "prevent" a render, as this can lead to bugs.
React.PureComponent
React.PureComponent
is similar to React.Component
. The difference between them is that React.Component
doesn't implement shouldComponentUpdate()
, but React.PureComponent
implements it with a shallow prop and state comparison.
If your React component's render()
function renders the same result given the same props and state, you can use React.PureComponent
for a performance boost in some cases.
Note
React.PureComponent
'sshouldComponentUpdate()
only shallowly compares the objects. If these contain complex data structures, it may produce false-negatives for deeper differences. Only extendPureComponent
when you expect to have simple props and state, or useforceUpdate()
when you know deep data structures have changed. Or, consider using immutable objects to facilitate fast comparisons of nested data.Furthermore,
React.PureComponent
'sshouldComponentUpdate()
skips prop updates for the whole component subtree. Make sure all the children components are also "pure".
Note
Unlike the
shouldComponentUpdate()
method on class components, theareEqual
function returnstrue
if the props are equal andfalse
if the props are not equal. This is the inverse fromshouldComponentUpdate
.
FAQs
What is memoization?
In computing, memoization or memoisation is an optimization technique used primarily to speed up computer programs by storing the results of expensive function calls and returning the cached result when the same inputs occur again.
Why is my component rendering twice?
Remove the <React.StrictMode>
tag as shown below and this behavior will go away however you may not want to remove it as it doesn't happen in production. For more information, see the Strict Mode Documentation or this stackoverflow question: Strict Mode Rendering Twice.
index.js
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
- <React.StrictMode>
{app}
- </React.StrictMode>
,
document.getElementById('root')
);
React.memo
Demo
Run the demo below and open the console to observe some wasted renders.
Steps:
Before beginning the demos in this chapter add the following css class if it doesn't already exist.
styles.css
.box {
border: 1px dashed;
padding: 30px;
}Paste the code below into
main.js
Open the application in a browser.
Open Chrome DevTools and switch to the
console
.Type in the add textbox to add an item and then click the add button.
Notice that every item in the list re-renders even though you only added one item.
Note: Updating or removing an item also causes everything to re-render.
Commment out the
ListItem
component.Uncomment the
ListItem
component below the original wrapped in aReact.memo
function.Refresh your browser.
Once again type in the add textbox to add an item and then click the add button.
Notice that only one item in the list re-renders since the other
ListItem
's are the same. You have successfully eliminated some wasted renders.The same issue of every item re-rendering actually existing when editing or removing an item. We have now fixed all of these wasted renders. If time permits feel free to change back to the non memoized implemention of
ListItem
to see the wasted renders.
function ID() {
return '_' + Math.random().toString(36).substr(2, 9);
}
class Item {
constructor(id, name) {
this.id = id;
this.name = name;
}
}
const initialItems = [
new Item(ID(), 'First Item'),
new Item(ID(), 'Second Item'),
new Item(ID(), 'Third Item'),
];
function LastRendered() {
return <p>Last Rendered: {new Date().toLocaleTimeString()}</p>;
}
function Form({ item, onSubmit, onCancel, buttonValue }) {
const [inputValue, setInputValue] = React.useState(item.name || '');
const handleChange = (event) => {
setInputValue(event.target.value);
};
const handleFormSubmit = (event) => {
event.preventDefault();
const submittedItem = {
id: item ? item.id : ID(),
name: inputValue,
};
onSubmit(submittedItem);
setInputValue('');
};
const handleCancel = (event) => {
event.preventDefault();
onCancel();
};
return (
<div className="box">
<LastRendered />
<form onSubmit={handleFormSubmit}>
<input value={inputValue} onChange={handleChange} />
<button>{buttonValue || 'Save'}</button>
{onCancel && (
<a href="#" onClick={handleCancel}>
cancel
</a>
)}
</form>
</div>
);
}
//1) Wasted renders when adding item
function ListItem({ item, onEdit, onRemove }) {
return (
<div className="box">
<LastRendered />
<p>
<span>{item.name}</span>
<button onClick={() => onEdit(item)}>Edit</button>
<button onClick={() => onRemove(item)}>Remove</button>
</p>
</div>
);
}
//2) Wasted renders fixed using React.memo and custom areEqual function
// const ListItem = React.memo(
// function ListItem({ item, onEdit, onRemove }) {
// return (
// <div className="box">
// <LastRendered />
// <p>
// <span>{item.name}</span>
// <button onClick={() => onEdit(item)}>Edit</button>
// <button onClick={() => onRemove(item)}>Remove</button>
// </p>
// </div>
// );
// },
// (previous, next) => previous.item === next.item
// );
//3) Wasted renders fixed using React.memo and useCallback
// const ListItem = React.memo(function ListItem({ item, onEdit, onRemove }) {
// return (
// <div className="box">
// <LastRendered />
// <p>
// <span>{item.name}</span>
// <button onClick={() => onEdit(item)}>Edit</button>
// <button onClick={() => onRemove(item)}>Remove</button>
// </p>
// </div>
// );
// });
function List({ items, onRemove, onUpdate }) {
const [editingItem, setEditingItem] = React.useState(null);
const handleEdit = (item) => {
setEditingItem(item);
};
// const handleEdit = React.useCallback(
// (item) => {
// setEditingItem(item);
// },
// [setEditingItem]
// );
const handleCancel = () => {
setEditingItem(null);
};
return (
<div className="box">
<LastRendered />
<ul>
{items.map((item) => (
<li key={item.id}>
{item === editingItem ? (
<Form item={item} onSubmit={onUpdate} onCancel={handleCancel} />
) : (
<ListItem item={item} onEdit={handleEdit} onRemove={onRemove} />
)}
</li>
))}
</ul>
</div>
);
}
function Container() {
const [items, setItems] = React.useState([]);
React.useEffect(() => setItems(initialItems), []);
const addItem = (item) => {
setItems([...items, item]);
};
const updateItem = (updatedItem) => {
let updatedItems = items.map((item) => {
return item.id === updatedItem.id
? Object.assign({}, item, updatedItem)
: item;
});
return setItems(updatedItems);
};
const removeItem = (removeThisItem) => {
const filteredItems = items.filter((item) => item.id != removeThisItem.id);
setItems(filteredItems);
};
// const removeItem = React.useCallback((removeThisItem) => {
// const filteredItems = items.filter((item) => item.id != removeThisItem.id);
// setItems(filteredItems);
// }, setItems);
return (
<div className="box">
<LastRendered />
<Form item="" onSubmit={addItem} buttonValue="Add" />
<List items={items} onRemove={removeItem} onUpdate={updateItem} />
</div>
);
}
function App() {
return (
<div>
<Container />
</div>
);
}
ReactDOM.createRoot(document.getElementById('root')).render(<App />);
Class Components
React.PureComponent
Demo
Run the demo below and open the console to observe some wasted renders.
Steps:
- Paste the code below into
main.js
- Open the application in a browser.
- Open Chrome DevTools and switch to the
console
. - Type in the add textbox to add an item and then click the add button.
- Notice that every item in the list re-renders even though you only added one item.
- Commment out the
ListItem
component (version labeled a). - Uncomment the
ListItem
component below the which extendsReact.PureComponent
function (version b). - Notice that the anonymous callback functions in the
onClick
event handlers where changed to usebind
so that the same version of the function would be passed as a prop every time instead of a new instance.- <button onClick={() => onEdit(item)}>Edit</button>
- <button onClick={() => onRemove(item)}>Remove</button>
+ <button onClick={onEdit.bind(this, item)}>Edit</button>
+ <button onClick={onRemove.bind(this, item)}>Remove</button> - Refresh your browser.
- Once again type in the add textbox to add an item and then click the add button.
- Notice that only one item in the list re-renders since the other
ListItem
's are the same. You have successfully eliminated a wasted render. - Try version c) of the component which uses the
shouldComponentUpdate
lifecyle method to control whether the component updates and only focuses on theitem
prop and ignores theonEdit
andonRemove
callbacks.
The same issue of every item re-rendering actually existing when editing or removing an item. We have now fixed all of these wasted renders. If time permits feel free to change back to the nonpure implemention of
ListItem
to see the wasted renders.
function ID() {
return '_' + Math.random().toString(36).substr(2, 9);
}
class Item {
constructor(id, name) {
this.id = id;
this.name = name;
}
}
const initialItems = [
new Item(ID(), 'First Item'),
new Item(ID(), 'Second Item'),
new Item(ID(), 'Third Item'),
];
class LastRendered extends React.Component {
render() {
return <p>Last Rendered: {new Date().toLocaleTimeString()}</p>;
}
}
//a) wasted renders
class ListItem extends React.Component {
render() {
const { item, onEdit, onRemove } = this.props;
return (
<div className="box">
<LastRendered />
<p>
<span>{item.name}</span>
<button onClick={() => onEdit(item)}>Edit</button>
<button onClick={() => onRemove(item)}>Remove</button>
</p>
</div>
);
}
}
//b) pure component
// class ListItem extends React.PureComponent {
// render() {
// const { item, onEdit, onRemove } = this.props;
// return (
// <div className="box">
// <LastRendered />
// <p>
// <span>{item.name}</span>
// <button onClick={onEdit.bind(this, item)}>Edit</button>
// <button onClick={onRemove.bind(this, item)}>Remove</button>
// </p>
// </div>
// );
// }
// }
//c) shouldComponentUpdate
// class ListItem extends React.Component {
// shouldComponentUpdate(previousProps) {
// return previousProps.item !== this.props.item;
// }
// render() {
// const { item, onEdit, onRemove } = this.props;
// return (
// <div className="box">
// <LastRendered />
// <p>
// <span>{item.name}</span>
// <button onClick={() => onEdit(item)}>Edit</button>
// <button onClick={() => onRemove(item)}>Remove</button>
// </p>
// </div>
// );
// }
// }
class List extends React.Component {
state = {
editingItem: null,
};
handleEditClick = (item) => {
this.setState({ editingItem: item });
};
handleCancel = (item) => {
this.setState({ editingItem: null });
};
render() {
const { items, onRemove, onUpdate } = this.props;
return (
<div className="box">
<LastRendered />
<ul>
{items.map((item) => (
<li key={item.id}>
{item === this.state.editingItem ? (
<Form
item={item}
onSubmit={onUpdate}
onCancel={this.handleCancel}
/>
) : (
<ListItem
item={item}
onEdit={this.handleEditClick}
onRemove={onRemove}
/>
)}
</li>
))}
</ul>
</div>
);
}
}
class Form extends React.Component {
state = {
inputValue: this.props.item.name || '',
};
handleChange = (event) => {
event.preventDefault();
this.setState({ inputValue: event.target.value });
};
handleFormSubmit = (event) => {
event.preventDefault();
const item = {
id: this.props.item ? this.props.item.id : ID(),
name: this.state.inputValue,
};
this.props.onSubmit(item);
this.setState({ inputValue: '' });
};
handleCancel = (event) => {
event.preventDefault();
this.props.onCancel();
};
render() {
return (
<div className="box">
<LastRendered />
<form onSubmit={this.handleFormSubmit}>
<input value={this.state.inputValue} onChange={this.handleChange} />
<button>{this.props.buttonValue || 'Save'}</button>
{this.props.onCancel && (
<a href="#" onClick={this.handleCancel}>
cancel
</a>
)}
</form>
</div>
);
}
}
class Container extends React.Component {
state = {
items: [],
};
componentDidMount() {
this.setState({ items: initialItems });
}
addItem = (item) => {
this.setState((state) => ({ items: [...state.items, item] }));
};
updateItem = (updatedItem) => {
this.setState((state) => {
let items = state.items.map((item) => {
return item.id === updatedItem.id
? Object.assign({}, item, updatedItem)
: item;
});
return { items };
});
};
removeItem = (removeThisItem) => {
this.setState((state) => {
const items = state.items.filter((item) => item.id != removeThisItem.id);
return { items };
});
};
render() {
return (
<div className="box">
<LastRendered />
<Form item="" onSubmit={this.addItem} buttonValue="Add" />
<List
items={this.state.items}
onRemove={this.removeItem}
onUpdate={this.updateItem}
/>
</div>
);
}
}
class App extends React.Component {
render() {
return (
<div>
<Container />
</div>
);
}
}
ReactDOM.createRoot(document.getElementById('root')).render(<App />);
Reference
- React.PureComponent
- React.memo
- Performance Tools
- Optimizing Performance
- Profiling Components with Chrome
- Why is immutability so important (or needed) in JavaScript?
- The DAO of Immutability
- Why Did You Render
- Why Did You Render Blog Post
- React Component Renders Too Often
- Flame Chart
- React Rendering Misconception
- You Probably Don't Need Derived State
- When to Re-Render a Component
- How to Update a Component's Props in React
- How to force a React component to re-render
- Pluralsight: Optimize Performance for React (payment required)
- Strict Mode Documentation
- Strict Mode Rendering Twice
- Optimizing React Native