[React] Optimization Cookbook
Containerization of State within Child React Components
The changing of state is the reason why components re-render. If you lift all of your state up into the parent container and pass it down to child components, any update to that state in the parent affects all other sibling components. Causing many useless re-renders. By simply moving the state into that child component, saves having to use React.Memo on all sibling components.
function Parent () {
const [count, setCount] = useState(0);
return (
<>
<Child count={count} setCount={setCount} />
<SecondChild />
<ThirdChild />
</>
)
}
function Child({count, setCount}) {
...
}
We lift Child component state to its Partent component, then passing the props down to the Child component.
🙅♀️ This is not a good solution, if the state changed (count), it will cause all the children components (Child, SecondChild, ThridChild) re-render; And SecondChild, ThirdChild have nothing todo with `count` state.
👌 Containerization the state to its component:
function Parent() {
return (
<>
<Child />
<SecondChild />
<ThirdChild />
</>
)
}
function Child() {
const [count, setCount] = useState(0)
return (...)
}
Now, if the count changes, it only re-render Child component, doesn't affect other components.
Optimize Function Components with React.memo
React.memo is similar to PureComponent when working with class components. This feature new to React 16 will not allow a functional component to render if its props hasn’t changed.
- It only works for functional component
- React.memo do shadow comparsion, If the component props is nested object, then React.memo won't do deep comparsion.
- You can pass in a custom comparsion function into React.memo
- If the wrapped component has `useState`, `useReducer` or `useContext`, the component will re-render.
Memoized Values with React useMemo
useMemo is a handy hook that we can use to avoid unnecessary computations of expensive functions. useMemo will only recompute the memorized value returned from the first parameter (called the create function) when a dependency in the second parameter array changes. Thus saving our app from re-computing a function each time our component re-renders.
- It works the computed value
const topProduct = React.useMemo(
() => data.sort((a, b) => b.progress - a.progress)[0], []
)
Memoize a Function with useCallback in React
The useCallback hook returns a memoized callback. To be more specific, useCallback will return a memorized version of the callback that only changes if one of the dependencies passed in the second parameter array has changed. This is particularly useful when working with optimized components and to goal is to avoid unnecessary re-renders, due to prop changes.
When you pass function into Child component, React consider, each time function is a new function... so it will re-render the component.
- It works for wrap a function which need to be passed to a child component
const CountButton = React.memo(function CountButton({ onClick, count }) {
console.log("CountButton Rendered");
return <button onClick={onClick}>{count}</button>;
});
const [count1, setCount1] = React.useState(0);
const likeIncrement = React.useCallback(() => setCount1((c) => c + 1), []);
const [count2, setCount2] = React.useState(0);
const dislikeIncrement = React.useCallback(() => setCount2((c) => c + 1), []);
<CountButton count={count1} onClick={likeIncrement} />
<CountButton count={count2} onClick={dislikeIncrement} />
Without, useCallback, you click on either Like or Dislike button, both buttons will be re-render, with useCallback, it will only re-render one.
Add the why-did-you-render Package to Catch Unnecessary Updates in React
Sometimes it can be difficult to understand why your components are rerendering. The why-did-you-update package will show what props and/or state updated that cause the re-render. Let’s install this package and figure out why our component is unnecessarily updating.
https://github.com/welldone-software/why-did-you-render
Code Split Components with React Lazy & Loadable Components
Code splitting is the process in which you only send to the browser the files necessary to render the current page. Instead of sending the entire app and all of its resources for all pages. Today there are many different ways to accomplish this React.lazy and Loadable Components.
https://reactjs.org/docs/code-splitting.html#reactlazy
https://github.com/gregberge/loadable-components
Using react lazy:
const Dashboard = react.lazy(() => import('./pages/Dashboard/Dashboard.js'))
- React lazy doesn't work with Server side rendering
Using loadable
import loadable from "@loadable/component"
const Dashboard = loadable(() => import("./pages/Dashboard/Dasjbpard.js"))
- Works with SSR