What is function Memoization and why should you care?
Tags: javascript, react, webdev, codenewbie
Published: 2020-04-28 07:23:55

Memoization is a general software engineering principal/idealogy that can be applied to code in any language. My examples and libraries will all be JavaScript.

## So What is Memoization?
Memoization is the principal of caching the result of a function call. If you call a function multiple times with the same arguments, you'll get the cached result every time. The logic in your function will not re-run when there's a cached result.

## Why/When Would I Ever Need This?
Memoization is great when you find functions being called over and over again (such as in a render call in React). Your function might have some complex logic that your performance would benefit from by not calling the same logic over and over.

*tl;dr performance for functions called multiple times with the same arguments.*

## Memoization in React
The concept of Memoization in React is exactly the same. We want to cache the result of a function call. Except in this scenario, our function returns JSX and our arguments are props.

If you have a parent being re-rendered, your child function will be called on every render, even if the props don't change. React provides us a `React.memo` utility and a `useMemo` hook which we can utilise in our functional components to prevent unnecessary re-renders.

We can also utilise normal Memoization in class methods and other JS functions in our react components. A traditional pattern in React class components was to react to prop changes through `componentWillReceiveProps`, apply some logic to a prop and set it in state. Now that `componentWillReceiveProps` is on the way to being deprecated, Memoization provides us a great alternative method to achieving the same result. See the examples section below.

https://reactjs.org/docs/react-api.html#reactmemo

## Some vanilla JS memoization Libraries
For general JavaScript, I'd recommend two battle-tested libraries instead of trying to implement yourself, which I've covered below.

### Lodash.memoize
Creates a memoization results map, meaning it will effectively store the history of all results for use in the future. 

Serialises only the first argument to string. Be careful about passing objects. Multiple arguments aren't compared.

Useful if you're calling the function from multiple places with different arguments.

https://lodash.com/docs/4.17.15#memoize

### Memoize One
Stores the last result of the function call. Will only ever compare the arguments to the last ones the function was called with.

Uses all the arguments for comparison between function calls. No serialisation of objects so you can pass anything.

Useful if you're only calling the memoized function from one place.

https://github.com/alexreardon/memoize-one

### Differences Between the Two
- Lodash memoize will *serialize* the arguments to use as a map key
- Lodash memoize will only use the *first* argument
- Memoize One will only remember the set of arguments/result of the *previous* function call. Lodash memoize will maintain a result map.

## How About Some Examples?
### A normal function
```js
import _memoize from 'lodash.memoize';
import memoizeOne from 'memoize-one';

const myFunc = users => users.filter(user => user.gender === 'female');

const myMemoizedFunc = _memoize(user => users.filter(user => user.gender === 'female'));

const myMemoizedOnceFunc = memoizeOne(user => users.filter(user => user.gender === 'female'));
```  
### React.memo
```js
import React, { memo } from 'react';

function MyFunctionalComponent {
  return <div />;
}

export default memo(MyFunctionalComponent);
```  
### Before/After, React class component real world scenario
#### Before
```js
import React, { Component } from 'react';

function filterUsers(users) {
  return users.filter(({ gender }) => gender === 'female');
}

export default class FemaleUserList extends Component {
  constructor(props) {
    super(props);
    
    const { allUsers } = props;
    
    this.state = {
      femaleUsers: filterUsers(allUsers)
    }
  }
  
  componentWillReceiveProps(nextProps) {
    const { allUsers } = nextProps;
    
    if (allUsers !== this.props.allUsers) {
      this.setState({
        femaleUsers: filterUsers(allUsers)
      });
    }
  }

  render() {
    const { femaleUsers } = this.state;
    
    return femaleUsers.map(User);
  }  
}
```
#### After
```js
import React, { Component } from 'react';
import memoizeOne from 'memoize-one';

export default class FemaleUserList extends Component {
  // We bind this function to the class now because the cached results are scoped to this class instance
  filterUsers = memoizeOne(users => users.filter(({ gender }) => gender === 'female'));
  
  render() {
    const { allUsers  } = this.props;
    const femaleUsers = this.filterUsers(allUsers);

    return femaleUsers.map(User);
  }
}
```
### A React form
```js
import React, { Component } from 'react';
import _memoize from 'lodash.memoize';

export default class FemaleUserList extends Component {
  // Yes, we can even return cached functions! This means we don't have to
  // keep creating new anonymous functions
  handleFieldChange = _memoize((fieldName) => ({ target: { value } }) => {
    this.setState({ [fieldName]: value });
  }); 

  
  render() {
    const { email, password } = this.state;

    return (
      <div>
        <input onChange={this.handleFieldChange('email')} value={email} />
        <input
          onChange={this.handleFieldChange('password')}
          value={password}
          type="password"
        />
      </div>
    );
  }
}
```

## Closing Words
Memoization is a great tool in a developer's arsenal. When used correctly and in the right places, it can provide great performance improvements.

Just be aware of the gotchas, especially when using *React.memo* and expecting things to re-render.