npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

rememo

v4.0.2

Published

Memoized selectors for Redux and other immutable object derivation

Downloads

234,355

Readme

Rememo

Memoized selectors for Redux and other immutable object derivation.

Usage

Rememo's default export is a function which accepts two arguments: the selector function whose return value is to be cached, and a second function which returns the reference or array of references upon which the selector's derivation depends. The return value is a new function which accepts the same arguments as the selector.

import createSelector from 'rememo';

const getTasksByCompletion = createSelector(
	// The expensive computation:
	(state, isComplete) =>
		state.todo.filter((task) => task.complete === isComplete),

	// The reference(s) upon which the computation depends:
	(state) => [state.todo]
);

// The selector will only calculate the return value once so long as the state
// `todo` reference remains the same
let completedTasks;
completedTasks = getTasksByCompletion(state, true); // Computed
completedTasks = getTasksByCompletion(state, true); // Returned from cache

Installation

Rememo is published as an npm package:

npm install rememo

Browser-ready versions are available from unpkg. The browser-ready version assigns itself on the global scope as window.rememo.

<script src="https://unpkg.com/rememo/dist/rememo.min.js"></script>
<script>
	var createSelector = window.rememo;

	// ...
</script>

API

Rememo's default export is a function:

createSelector(
	selector: (...args: any[]) => any,
	getDependants?: (...args: any[]) => any[],
): (...args: any[]) => any

The returned function is a memoized selector with the following signature:

memoizedSelector(source: object, ...args: any[]): any

It's expected that the first argument to the memoized function is the source from which the selector operates. It is ignored when considering whether the argument result has already been cached.

The memoized selector function includes two additional properties:

  • clear(): When invoked, resets memoization cache.
  • getDependants(source: Object, ...args: any[]): The dependants getter for the selector.

The getDependants property can be useful when creating selectors which compose other memoized selectors, in which case the dependants are the union of the two selectors' dependants:

const getTasksByCompletion = createSelector(
	(state, isComplete) =>
		state.todo.filter((task) => task.complete === isComplete),
	(state) => [state.todo]
);

const getTasksByCompletionForCurrentDate = createSelector(
	(state, isComplete) =>
		getTasksByCompletion(state, isComplete).filter(
			(task) => task.date === state.currentDate
		),
	(state, isComplete) => [
		...getTasksByCompletion.getDependants(state, isComplete),
		state.currentDate,
	]
);

Motivation

While designed specifically for use with Redux, Rememo is a simple pattern for efficiently deriving values from any immutable data object. Rememo takes advantage of Redux's core principles of data normalization and immutability. While tracking normalized data in a Redux store is beneficial for eliminating redudancy and reducing overall memory storage, in doing so it sacrifices conveniences that would otherwise make for a pleasant developer experience. It's for this reason that a selector pattern can be desirable. A selector is nothing more than a function which receives the current state and optionally a set of arguments to be used in determining the calculated value.

For example, consider the following state structure to describe a to-do list application:

const state = {
	todo: [
		{ text: 'Go to the gym', complete: true },
		{ text: 'Try to spend time in the sunlight', complete: false },
		{ text: 'Laundry must be done', complete: true },
	],
};

If we wanted to filter tasks by completion, we could write a simple function:

function getTasksByCompletion(state, isComplete) {
	return state.todo.filter((task) => task.complete === isComplete);
}

This works well enough and requires no additional tools, but you'll observe that the filtering we perform on the set of to-do tasks could become costly if we were to have thousands of tasks. And this is just a simple example; real-world use cases could involve far more expensive computation. Add to this the very real likelihood that our application might call this function many times even when our to-do set has not changed.

Furthermore, when used in combination with React.PureComponent or react-redux's connect — which creates pure components by default — it is advisable to pass unchanging object and array references as props on subsequent renders. A selector which returns a new reference on each invocation (as occurs with Array#map or Array#filter), your component will needlessly render even if the underlying data does not change.

This is where Rememo comes in: a Rememo selector will cache the resulting value so long as the references upon which it depends have not changed. This works particularly well for immutable data structures, where we can perform a trivial strict equality comparison (===) to determine whether state has changed. Without guaranteed immutability, equality can only be known by deeply traversing the object structure, an operation which in many cases is far more costly than the original computation.

In our above example, we know the value of the function will only change if the set of to-do's has changed. It's in Rememo's second argument that we describe this dependency:

const getTasksByCompletion = createSelector(
	(state, isComplete) =>
		state.todo.filter((task) => task.complete === isComplete),
	(state) => [state.todo]
);

Now we can call getTasksByCompletion as many times as we want without needlessly wasting time filtering tasks when the todo set has not changed.

Testing

To simplify testing of memoized selectors, the function returned by createSelector includes a clear function:

const getTasksByCompletion = require('../selector');

// Test licecycle management varies by runner. This example uses Mocha.
beforeEach(() => {
	getTasksByCompletion.clear();
});

Alternatively, you can create separate references (exports) for your memoized and unmemoized selectors, then test only the unmemoized selector.

Refer to Rememo's own tests as an example.

FAQ

How does this differ from Reselect, another selector memoization library?

Reselect and Rememo largely share the same goals, but have slightly different implementation semantics. Reselect optimizes for function composition, requiring that you pass as arguments functions returning derived data of increasing specificity. Constrasting it to our to-do example above, with Reselect we would pass two arguments: a function which retrieves todo from the state object, and a second function which receives that set as an argument and performs the completeness filter. The distinction is not as obvious with a simple example like this one, and can be seen more clearly with examples in Reselect's README.

Rememo instead encourages you to consider the derivation first-and-foremost without requiring you to build up the individual dependencies ahead of time. This is especially convenient if your computation depends on many disparate state paths, or if you choose not to memoize all selectors and would rather opt-in to caching at your own judgment. Composing selectors is still straight-forward in Rememo if you subscribe to the convention of passing state always as the first argument, since this enables your selectors to call upon other each other passing the complete state object.

License

Copyright 2018-2022 Andrew Duthie

Released under the MIT License.