Memoization with Reselect

Introduction

Memoization is a powerful optimization technique that saves time by storing the results of expensive function calls and reusing them when the same inputs occur. In the context of JavaScript and React, Reselect is a popular library that applies memoization to the Redux store, optimizing the computation of derived data.

Prerequisites

Before diving into memoization with Reselect, you should have a basic understanding of:

  • JavaScript (ES6)

  • React

  • Redux

What is Reselect?

Reselect is a simple selector library for Redux, inspired by getters in NuclearJS(https://optimizely.github.io/nuclear-js/), subscriptions in re-frame, and this proposal from speedskater. It provides a way to compute derived data from your Redux store, allowing Redux to store the minimal possible state.

Why Use Reselect?

Reselect offers two main benefits:

  1. Computation Efficiency: Reselect selectors are efficient. A selector is not recomputed unless one of its arguments changes.

  2. Encapsulation: Reselect selectors can be composed together, allowing more complex selectors to be built from simpler ones.

How Does Reselect Work?

Reselect selectors are created using the createSelector function. The resulting selector function has memoization capabilities. If the Redux state changes in a way that causes the value(s) you’re selecting to remain the same, the selector returns the previously computed result instead of recalculating it.

Example: A Simple Counter

Let’s create a simple counter application using React, Redux, and Reselect.

// actions.js
export const increment = () => ({ type: 'INCREMENT' });
export const decrement = () => ({ type: 'DECREMENT' });

// reducer.js
export const counter = (state = 0, action) => {
  switch (action.type) {
    case 'INCREMENT':
      return state + 1;
    case 'DECREMENT':
      return state - 1;
    default:
      return state;
  }
};

// selectors.js
import { createSelector } from 'reselect';

const getCounter = state => state.counter;

export const getCounterValue = createSelector(
  getCounter,
  counter => counter
);

// Counter.js
import React from 'react';
import { connect } from 'react-redux';
import { increment, decrement } from './actions';
import { getCounterValue } from './selectors';

const Counter = ({ counter, increment, decrement }) => (
  <div>
    <button onClick={decrement}>-</button>
    {counter}
    <button onClick={increment}>+</button>
  </div>
);

const mapStateToProps = state => ({
  counter: getCounterValue(state),
});

const mapDispatchToProps = { increment, decrement };

export default connect(mapStateToProps, mapDispatchToProps)(Counter);

In this example, the getCounterValue selector uses Reselect to efficiently compute the counter value from the Redux state. The Counter component uses this selector to access the current counter value.

Conclusion

Reselect provides an efficient way to compute derived data from the Redux store via memoization. By understanding and leveraging this library, you can optimize your React applications and ensure they remain performant even as they scale.