React Component Render Optimization Techniques
Ref: https://www.developerway.com/posts/react-re-renders-guide
=================
A component render is considered "wasted" if it returns the same requested elements as before. Normally, UI = f(props + state)
, so if props and state haven't changed, the UI output should be the same.
We can optimize performance by skipping component renders if props haven't changed. This also skips the entire subtree of that component.
Primary: wrap a component with the
React.memo()
higher-order component- Automatically checks for props changes with "shallow equality" comparison
- Accepts a custom comparison callback as an option
Class components:
shouldComponentUpdate
orReact.PureComponent
=================
// ❌ BAD!
// Antipattern: Creating components in render function
// This creates a new `ChildComponent` reference every time!
function ParentComponent() {
function ChildComponent() {
return <div>Hi</div>
}
return <ChildComponent />
}
// ✅ GOOD
// This only creates one component type reference
function ChildComponent() {
return <div>Hi</div>
}
function ParentComponent() {
return <ChildComponent />
}
// ✅ GOOD
// Use props.children
function Parent({children}) {
const [counter, setCounter] = useState(0)
// Consistent `props.children` element reference as state changes
return (
<div>
<button onClick={increment}>Increment</button>
{children}
</div>
)
}
// later
return <Parent><SomeChild /></Parent>
// ✅ GOOD
// Use useMemo to save an element reference
function ComponentA({data}) {
// Consistent `ChildComponent` element reference
const memoizedChild = useMemo(() => {
return <ChildComponent data={data} />;
}, [data])
return <div>{memoizedChild}</div>
}
=================
Antipattern: Creating components in render function
Creating components inside render function of another component is an anti-pattern that can be the biggest performance killer. On every re-render React will re-mount this component (i.e. destroy it and re-create it from scratch), which is going to be much slower than a normal re-render. On top of that, this will lead to such bugs as:
- possible “flashes” of content during re-renders
- state being reset in the component with every re-render
- useEffect with no dependencies triggered on every re-render
- if a component was focused, focus will be lost
Preventing re-renders with composition: moving state down
Preventing re-renders with composition: children as props
Preventing re-renders with composition: components as props
=================
Immutability and Rerendering
React.memo()
does "shallow equality" checks like props.someValue !== prevProps.someValue
, assuming immutable updates will change references. Hooks with dependencies (useMemo/useCallback/useEffect
) work similarly.
If you mutate, then someValue is the same reference, and those components will assume nothing has changed.
In addition: useState
and useReducer
expect new state references in order to re-render. If you pass in the same state reference, React will bail out of the update!
React, and the rest of the React ecosystem, assume immutable updates. Any time you mutate, you run the risk of bugs. Don't do it!!!