Should you default to React.memo() or useMemo()?
Tags: javascript, react, performance, webdev
Published: 2022-02-08 16:19:55

You might have faced slow renders in react application? When such a situation happens we find ourselves inclined to use `React.memo` or `useMemo`. We use `React.memo` to bail out of re-rendering by wrapping subtree in `React.memo.` This works pretty well as an optimization patch but with big real-world apps using it mindlessly can make you suffer *“death by thousand cuts”* and you may end up wrapping everything that you think is slow with `React.memo.` 

With this comes using `useMemo` and `useCallback` which you may use for [memoized](https://en.wikipedia.org/wiki/Memoization) computational values, functions, and handlers. This adds to overall code complexity and you may end up running more code for React to determine changed values, compare them and memoize them. Sometimes this might be the only solution but there are approaches that you can try before you `memoize` things. 

Consider the following code sample:

```js 
import React, { useState } from "react";

// [App.js]
function App() {
  const [name, setName] = useState('');
  return (
    <div>
      <input type="text" value={name} 
        onChange={(e) => setName(e.target.value)} />
      <SlowSubtree />
    </div>
  );
}

function SlowSubtree() {
  sleep(500); // try increasing time here 💣
  return <p>Artifically Slow subtree</p>;
}

// [utils.js]
function sleep(time) {
  const exitAt = Date.now() + time;
  while (exitAt > Date.now()) {
    // simulate expensive subtree
  }
}
```

In the code example, we have artificially simulated an expensive sub-tree with `<SlowSubtree />` component. Which gets re-rendered on a change in input. As we type in input we set the `name` state thus causing a re-render of the `App` component, which then renders `<SlowSubtree />`. The quick fix here is wrapping SlowSubtreecomponent in a `React.memo` as shown below:

```js
import React, { memo } from "react";

// [App.js]
function SlowSubtreeComponent() {
  sleep(500);
  return <p>Artifically Slow subtree</p>
}

const SlowSubtree = memo(SlowSubtreeComponent);
```
`React.memo()` is a quick fix but can we avoid using it? Following are some approaches:
 ### Debounce set-state

In the example, we are setting the `name` state with every keystroke and causing re-rendering each time input changes, setting the state on every keystroke is wastage here. What we can do is we can debounce set state calls to prevent rendering with each keystroke. I consider this as a bit of a ***hacky*** approach but I’ve put this here to bring this to your notice.

```js
import React, { 
  useState, 
  useMemo, 
  useLayoutEffect, 
  useRef 
} from "react";

// [App.js]
function App() {
  const [name, setName] = useState('');
  const debounceOnChange = useDebounceFn(
    (e) => setName(e.target.value)
  );
  
  return (
    <div>
      <input type="text" onChange={debounceOnChange} />
      <SlowSubtree />
    </div>
  );
}

function SlowSubtree() {
  sleep(500); // try increasing time here 💣
  return <p>Artifically Slow subtree</p>;
}

// [utils.js]
function sleep(time) {
  const exitAt = Date.now() + time;
  while (exitAt > Date.now()) {
    // simulate expensive subtree
  }
}

// [hooks.js]
import debounce from "lodash.debounce";

function useDebounceFn(callbackFn, delay = 500) {
  const callbackFnRef = useRef(callbackFn);
  
  useLayoutEffect(() => {
    callbackFnRef.current = callbackFn;
  });
  
  return useMemo(
    () => debounce(
       (...args) => callbackFnRef.current(...args), delay),
    [delay]
  );
}

```
### State relocation

Note that the `SlowSubtree` the component renders because of a state change in the parent component. The changing part here is `name` state with `<input/>` while `SlowSubtree` is not changing. We can split and move state down in its separate component like shown below:

```js
import React, { useState } from "react";

// [App.js]
function App() {
  const [name, setName] = useState('');
  return (
    <div>
      <NameInput />
      <SlowSubtree />
    </div>
  );
}

function NameInput() {
  const [name, setName] = useState('');
  return  (
    <input 
      type="text" 
      value={name} 
      onChange={(e) => setName(e.target.value)} 
    />
  );
}

function SlowSubtree() {
  sleep(500); // try increasing time here 💣
  return <p>Artifically Slow subtree</p>;
}

// [utils.js]
function sleep(time) {
  const exitAt = Date.now() + time;
  while (exitAt > Date.now()) {
    // simulate expensive subtree
  }
}
```
### Render as a child

It is not required to move the state down in its own `NameInput` component we can also move the state up and leverage a pattern called* render as child.*
This pattern is very similar to the render props approach but instead of passing components to a render prop, we use `props.children` instead. Here we will lift the state up in its own component and wrap `SlowSubtree` component with it.

```js
import React, { useState } from "react";

// [App.js]
function App() {
  return (
    <NameComponent>
      <SlowSubtree />
    </NameComponent>
  );
}

function NameComponent(props) {
  const [name, setName] = useState('');
  return (
    <div>
      <input 
         type="text" 
         value={name} 
         onChange={(e) => setName(e.target.value)} 
       />
      {props.children}
    </div>
  );
}

function SlowSubtree() {
  sleep(500); // try increasing time here 💣
  return <p>Artifically Slow subtree</p>;
}

// [utils.js]
function sleep(time) {
  const exitAt = Date.now() + time;
  while (exitAt > Date.now()) {
    // simulate expensive subtree
  }
}
```
When name state changed `NameComponent` re-render but as it still gets the same `children` prop as last time so React doesn’t need to visit `SlowSubtreesubtree.` And as a result `<SlowSubtree />` doesn’t re-render.

I have personally used this approach many times to prevent the re-rendering of child subtree. This pattern is also used in layout components where the wrapper decided the layout and styles of its children. For example [Material-UI Card component](https://mui.com/components/cards/). The layout component may or may not maintain the state but they generally receive the child as a prop or render-props.

### Conclusion:
Before you use a `React.memo()` or `useMemo()` again you should stop and think to see if you can split part that changes from the parts that don’t change. So take into account if state relocation can help before you settle with the `memo.`