Security News
tea.xyz Spam Plagues npm and RubyGems Package Registries
Tea.xyz, a crypto project aimed at rewarding open source contributions, is once again facing backlash due to an influx of spam packages flooding public package registries.
rememo
Advanced tools
Changelog
v4.0.2 (2022-10-04)
Readme
Memoized selectors for Redux and other immutable object derivation.
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
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>
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,
]
);
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.
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.
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.
Copyright 2018-2022 Andrew Duthie
Released under the MIT License.
FAQs
Memoized selectors for Redux and other immutable object derivation
The npm package rememo receives a total of 45,977 weekly downloads. As such, rememo popularity was classified as popular.
We found that rememo demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
Tea.xyz, a crypto project aimed at rewarding open source contributions, is once again facing backlash due to an influx of spam packages flooding public package registries.
Security News
As cyber threats become more autonomous, AI-powered defenses are crucial for businesses to stay ahead of attackers who can exploit software vulnerabilities at scale.
Security News
UnitedHealth Group disclosed that the ransomware attack on Change Healthcare compromised protected health information for millions in the U.S., with estimated costs to the company expected to reach $1 billion.