Memoisation in React

Memoisation is an optimisation technique that caches the result of previous computations so that they can be quickly accessed without repeating the same computation.

React introduces quite a few memoisation functions being React.memo, useMemo and useC…


This content originally appeared on DEV Community and was authored by Benjamin Liu

Memoisation is an optimisation technique that caches the result of previous computations so that they can be quickly accessed without repeating the same computation.

React introduces quite a few memoisation functions being React.memo, useMemo and useCallback.

1. React.memo

React.memo is a higher order component when wrapped around a component, memoises the result of the component and does a shallow comparison before the next render. If the new props are the same the component doesn't re-render and uses the memoised result.

By default memo does a shallow comparison of props, however, the second argument allows you to define a custom equality check function. From React's official docs:

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);

However, if you're looking to do a deep comparison between 2 values and want to take the easy route you can use isEqual from lodash.

Now let's have a look at this example:

// App.js
import React, { useState } from "react";

import Child from "./Child.js";

const App = () => {
  const [counter, setCounter] = useState(0);
  const [text, setText] = useState("");

  return (
    <div className="App">
      <input
        onChange={(e) => setText(e.target.value)}
        type="text"
        value={text}
      />
      <button onClick={() => setCounter(counter + 1)}>+ 1</button>
      <Child counter={counter} />
    </div>
  );
};

export default App;

In this case we have a parent component called App which takes in a <Child /> component.

import React from "react";

const Child = ({ counter }) => {
  console.log("rendering...");

  return <div>Count: {counter}</div>;
};

export default Child;

If you open up Console you will notice that given each keystroke in the input field the <Child /> component re-renders. Obviously this doesn't have any performance overhead at this point in time, but imagine if the Child component had child components of its own with state. Then you'd trigger a re-render of all components associated with the parent, that'd definitely add overhead to your application.

To prevent child components from unnecessarily re-rendering like that we have to use React.memo. All we need to do is wrap our Child component in our memo and you see that no matter what we type in the input field it doesn't trigger a re-render of the <Child /> component.

import React, { memo } from "react";

const Child = ({ counter }) => {
  console.log("rendering...");

  return <div>Count: {counter}</div>;
};

export default memo(Child);

However, what if we wanted to pass down functions or anything that isn't a primitive value such as objects since memo only does a shallow comparison? A shallow comparison in this case means it only checks if the props that you're passing down is referencing the same place in memory.

So let's say we want to update the counter from <Child /> so we do something like this:

// App.js
import React, { useState } from "react";

import Child from "./Child.js";

const App = () => {
  const [counter, setCounter] = useState(0);
  const [text, setText] = useState("");

  const updateCounterHandler = () => {
    setCounter(counter + 1);
  };

  return (
    <div className="App">
      <input
        onChange={(e) => setText(e.target.value)}
        type="text"
        value={text}
      />
      <button onClick={() => setCounter(counter + 1)}>+ 1</button>
      <Child counter={counter} updateCounter={updateCounterHandler} />
    </div>
  );
};

export default App;

and within Child.js:

import React, { memo } from "react";

const Child = ({ counter, updateCounter: pushUpdateCounter }) => {
  console.log("rendering...");

  return (
    <div>
      <strong>Count: {counter}</strong>
      <button onClick={pushUpdateCounter}>Update Counter</button>
    </div>
  );
};

export default memo(Child);

However, you will notice that the <Child /> component still gets rendered whenever we type something into the input field. This is because the updateCounterHandler inside App.js gets recreated each time the state changes.

So the correct way to handle callback functions with memo is by using useCallback.

2. useCallback

useCallback is a hook that comes with react that returns a memoised function. It takes in 2 arguments, the first one being the callback function, the second being an array of dependencies.

So all that needs to be done is wrap useCallback around our updateCounterHandler function to prevent the <Child /> component from re-rendering whenever we type in the input field.

const updateCounterHandler = useCallback(() => {
    setCounter(counter + 1);
  }, [counter]);

3. useMemo

Like useCallback, useMemo is a hook that takes in a function, however, instead of returning a memoised function it returns a memoised value. This makes it useful when performing heavy calculations.

import React, { memo, useMemo } from "react";

const Child = ({ counter, updateCounter: pushUpdateCounter }) => {
  console.log("rendering...");

  const outputNumber = useMemo(() => {
    let output = 0;

    for (let i = 0; i < 10000000; i++) {
      output++;
    }

    return output;
  }, []);

  return (
    <div>
      <strong>Count: {counter}</strong>
      <div>Output Number: {outputNumber}</div>
      <button onClick={pushUpdateCounter}>Update Counter</button>
    </div>
  );
};

export default memo(Child);

Using useMemo in the example above, we're able to cache the return value of outputNumber, so that we're not recalling the function each time.

After learning these techniques, I hope you're able to apply it to where it's truly needed, because premature optimisation is the root of all evil! It's about finding the fine line between compromising space and time as speed optimisation techniques such as memoisation eat up space (RAM) in return for a faster time. So always question yourself before optimising your code, "do the performance gains really justify the usage?".


This content originally appeared on DEV Community and was authored by Benjamin Liu


Print Share Comment Cite Upload Translate Updates
APA

Benjamin Liu | Sciencx (2021-03-13T03:22:49+00:00) Memoisation in React. Retrieved from https://www.scien.cx/2021/03/13/memoisation-in-react/

MLA
" » Memoisation in React." Benjamin Liu | Sciencx - Saturday March 13, 2021, https://www.scien.cx/2021/03/13/memoisation-in-react/
HARVARD
Benjamin Liu | Sciencx Saturday March 13, 2021 » Memoisation in React., viewed ,<https://www.scien.cx/2021/03/13/memoisation-in-react/>
VANCOUVER
Benjamin Liu | Sciencx - » Memoisation in React. [Internet]. [Accessed ]. Available from: https://www.scien.cx/2021/03/13/memoisation-in-react/
CHICAGO
" » Memoisation in React." Benjamin Liu | Sciencx - Accessed . https://www.scien.cx/2021/03/13/memoisation-in-react/
IEEE
" » Memoisation in React." Benjamin Liu | Sciencx [Online]. Available: https://www.scien.cx/2021/03/13/memoisation-in-react/. [Accessed: ]
rf:citation
» Memoisation in React | Benjamin Liu | Sciencx | https://www.scien.cx/2021/03/13/memoisation-in-react/ |

Please log in to upload a file.




There are no updates yet.
Click the Upload button above to add an update.

You must be logged in to translate posts. Please log in or register.