) => ({
one,
two,
});
export default moize.serializeWith(JSON.stringify)(fn);
```
**NOTE**: If you want to use the default [`serializer`](#serializer), you should use [`moize.serialize`](#moizeserialize):
```ts
moize.serialize(customSerializer)(fn);
```
## moize.shallow
Pre-applies the [`isShallowEqual`](#isshallowequal) option.
```ts
import moize from 'moize';
const fn = (one: string, two: string) => `${one} ${two}`;
export default moize.shallow(fn);
```
## moize.transformArgs
Pre-applies the [`transformArgs`](#transformargs) option.
```ts
import moize from 'moize';
const fn = ([one, two]: string[]) => [`${one} ${two}`];
export default moize.transformArgs(fn);
```
## moize.updateCacheForKey
Pre-applies the [`updateCacheForKey`](#updatecacheforkey) option.
```ts
import moize from 'moize';
let lastUpdated = Date.now();
const fn = () => {
const now = Date.now();
const last = lastUpdated;
lastUpdate = now;
// its been more than 5 minutes since last update
return last + 300000 < now;
};
export default moize.updateCacheForKey(fn);
```
# useMoize hook
If you are using React 16.8+ and are using hooks, you can easily create a custom `useMoize` hook for your project:
```ts
import { useRef } from 'react';
export function useMoize(fn, args, options) {
const moizedFnRef = useRef(moize(fn, options));
return moizedFnRef.current(...args);
}
```
Which can then be used as such:
```tsx
import React from 'react';
import { useMoize } from './moize-hooks';
function MyComponent({ first, second, object }) {
// standard usage
const sum = useMoize((a, b) => a + b, [first, second]);
// with options
const deepSum = useMoize((obj) => obj.a + obj.b, [object], {
isDeepEqual: true,
});
return (
Sum of {first} and {second} is {sum}. Sum of {object.a} and{' '}
{object.b} is {deepSum}.
);
}
```
Naturally you can tweak as needed for your project (default options, option-specific hooks, etc).
**NOTE**: This is very similar to [`useCallback`](https://reactjs.org/docs/hooks-reference.html#usecallback) built-in hook, with two main differences:
- There is a third parameter passed (the [`options`](#configuration-options) passed to `moize`)
- The second argument array is the list of arguments passed to the memoized function
In both `useCallback` and `useMemo`, the array is a list of _dependencies_ which determine whether the funciton is called. These can be different than the arguments, although in general practice they are equivalent. The decision to use them directly was both for this common use-case reasons, but also because the implementation complexity would have increased substantially if not.
# Composition
Starting with version `2.3.0`, you can compose `moize` methods. This will create a new memoized method with the original function that shallowly merges the options of the two setups. Example:
```tsx
import moize from 'moize';
const Component = (props: Record) => ;
// memoizing with react, as since 2.0.0
const MemoizedFoo = moize.react(Component);
// creating a separately-memoized method that has maxSize of 5
const LastFiveFoo = moize.maxSize(5)(MemoizedFoo);
```
You can also create an options-first curriable version of `moize` if you only pass the options:
```ts
import moize from 'moize';
// creates a function that will memoize what is passed
const limitedSerializedMoize = moize({ maxSize: 5, serialize: true });
const getWord = (bird) => `${bird} is the word`;
const moizedGetWord = limitedSerializedMoize(getWord);
```
You can also combine all of these options with `moize.compose` to create `moize` wrappers with pre-defined options.
```ts
import moize from 'moize';
// creates a moizer that will have the options of
// {isReact: true, maxAge: 5000, maxSize: 5}
const superLimitedReactMoize = moize.compose(
moize.react,
moize.maxSize(5),
moize.maxAge(5000)
);
```
# Collecting statistics
As-of version 5, you can collect statistics of moize to determine if your cached methods are effective.
```ts
import moize from 'moize';
moize.collectStats();
const fn = (one: string, two: string) => [one, two];
const moized = moize(fn);
moized('one', 'two');
moized('one', 'two');
moized.getStats(); // {"calls": 2, "hits": 1, "usage": "50%"}
```
**NOTE**: It is recommended not to activate this in production, as it will have a performance decrease.
## Stats methods
## clearStats
Cear statistics on `moize`d functions.
```ts
moize.clearStats(); // clears all stats
moize.clearStats('profile-name'); // clears stats only for 'profile-name'
```
## collectStats
Set whether collecting statistics on `moize`d functions.
```ts
moize.collectStats(true); // start collecting stats
moize.collectStats(); // same as passing true
moize.collectStats(false); // stop collecting stats
```
**NOTE**: If collecting statistics, it is recommended to provide a custom [`profileName`](#profilename) or use [`moize.profile()`](#moizeprofile) for all memoized functions. This allows easier mapping of resulting statistics to their origin function when it has a common name or is anonymous.
## getStats([profileName])
Get the statistics for a specific function, or globally.
```ts
moize.collectStats();
const fn = (one: string, two: string) => [one, two];
const moized = moize(fn);
const otherFn = (one: string[]) => one.slice(0, 1);
const otherMoized = moize(otherFn, { profileName: 'otherMoized' });
moized('one', 'two');
moized('one', 'two');
moized.getStats(); // {"calls": 2, "hits": 1, "usage": "50%"}
otherMoized(['three']);
moize.getStats('otherMoized'); // {"calls": 1, "hits": 0, "usage": "0%"}
moize.getStats();
/*
{
"calls": 3,
"hits": 1,
"profiles": {
"fn at Object..src/utils.js (http://localhost:3000/app.js:153:68)": {
"calls": 2,
"hits": 1,
"usage": "50%"
},
"otherMoized": {
"calls": 1,
"hits": 0,
"usage": "0%"
}
},
"usage": "33.3333%"
}
*/
```
# Introspection
## isCollectingStats
Are statistics being collected on memoization usage.
```ts
moize.collectStats(true);
moize.isCollectingStats(); // true
moize.collectStats(false);
moize.isCollectingStats(); // false
```
## isMoized
Is the function passed a moized function.
```ts
const fn = () => {};
const moizedFn = moize(fn);
moize.isMoized(fn); // false
moize.isMoized(moizedFn); // true
```
# Direct cache manipulation
The cache is available on the `moize`d function as a property, and while it is not recommended to modify it directly, that option is available for edge cases.
## cache
The shape of the `cache` is as follows:
```ts
type Cache = {
keys: any[][];
size: number;
values: any[];
};
```
Regardless of how the key is transformed, it is always stored as an array (if the value returned is not an array, it is coalesced to one).
**NOTE**: The order of `keys` and `values` should always align, so be aware when manually manipulating the cache that you need to manually keep in sync any changes to those arrays.
## cacheSnapshot
The `cache` is mutated internally for performance reasons, so logging out the cache at a specific step in the workflow may not give you the information you need. As such, to help with debugging you can request the `cacheSnapshot`, which has the same shape as the `cache` but is a shallow clone of each property for persistence.
There are also convenience methods provided on the `moize`d function which allow for programmatic manipulation of the cache.
## add(key, value)
This will manually add the _value_ at _key_ in cache if _key_ does not already exist. _key_ should be an `Array` of values, meant to reflect the arguments passed to the method.
```ts
// single parameter is straightforward
const memoized = moize((item: string) => item: string);
memoized.add(['one'], 'two');
// pulls from cache
memoized('one');
```
**NOTE**: This will only add `key`s that do not exist in the cache, and will do nothing if the `key` already exists. If you want to update keys that already exist, use [`update`](#updatekey-value).
## clear()
This will clear all values in the cache, resetting it to an empty state.
```ts
const memoized = moize((item: string) => item);
memoized.clear();
```
## get(key)
Returns the value in cache if the key matches, else returns `undefined`. _key_ should be an `Array` of values, meant to reflect the arguments passed to the method.
```ts
const memoized = moize((one: string, two: string) => [one, two);
memoized('one', 'two');
console.log(memoized.get(['one', 'two'])); // ["one","two"]
console.log(memoized.get(['two', 'three'])); // undefined
```
## getStats()
Returns the statistics for the function.
```ts
moize.collectStats();
const memoized = moize((one: string, two: string) => [one, two);
memoized('one', 'two');
memoized('one', 'two');
console.log(memoized.getStats()); // {"calls": 2, "hits": 1, "usage": "50%"}
```
**NOTE**: You must be collecting statistics for this to be populated.
## has(key)
This will return `true` if a cache entry exists for the _key_ passed, else will return `false`. _key_ should be an `Array` of values, meant to reflect the arguments passed to the method.
```ts
const memoized = moize((one: string, two: string) => [one, two]);
memoized('one', 'two');
console.log(memoized.has(['one', 'two'])); // true
console.log(memoized.has(['two', 'three'])); // false
```
## keys()
This will return a list of the current keys in `cache`.
```ts
const memoized = moize.maxSize(2)((item: any) => item);
memoized('one');
memoized({ two: 'three' });
const keys = memoized.keys(); // [['one'], [{two: 'three'}]]
```
## remove(key)
This will remove the provided _key_ from cache. _key_ should be an `Array` of values, meant to reflect the arguments passed to the method.
```ts
const memoized = moize((item: { one: string }) => item);
const arg = { one: 'one' };
memoized(arg);
memoized.remove([arg]);
// will re-execute, as it is no longer in cache
memoized(arg);
```
**NOTE**: This will only remove `key`s that exist in the cache, and will do nothing if the `key` does not exist.
## update(key, value)
This will manually update the _value_ at _key_ in cache if _key_ exists. _key_ should be an `Array` of values, meant to reflect the arguments passed to the method.
```ts
// single parameter is straightforward
const memoized = moize((item: string) => item);
memoized.add(['one'], 'two');
// pulls from cache
memoized('one');
```
**NOTE**: This will only update `key`s that exist in the cache, and will do nothing if the `key` does not exist. If you want to add keys that do not already exist, use [`add`](#addkey-value).
## values()
This will return a list of the current values in `cache`.
```ts
const memoized = moize.maxSize(2)((item: string | { two: string }) => ({
item,
}));
memoized('one');
memoized({ two: 'three' });
const values = memoized.values(); // [{item: 'one'}, {item: {two: 'three'}}]
```
# Benchmarks
All values provided are the number of operations per second calculated by the [Benchmark suite](https://benchmarkjs.com/), where a higher value is better. Each benchmark was performed using the default configuration of the library, with a fibonacci calculation based on a starting parameter of `35`, using single and multiple parameters with different object types. The results were averaged to determine overall speed across possible usage.
**NOTE**: `lodash`, `ramda`, and `underscore` do not support multiple-parameter memoization without use of a `resolver` function. For consistency in comparison, each use the same `resolver` that returns the result of `JSON.stringify` on the arguments.
| Name | Overall (average) | Single (average) | Multiple (average) | single primitive | single array | single object | multiple primitive | multiple array | multiple object |
| ------------ | ----------------- | ---------------- | ------------------ | ---------------- | -------------- | -------------- | ------------------ | -------------- | --------------- |
| **moize** | **71,177,801** | **98,393,482** | **43,962,121** | **139,808,786** | **97,571,202** | **57,800,460** | **44,509,528** | **44,526,039** | **42,850,796** |
| lru-memoize | 48,391,839 | 64,270,849 | 32,512,830 | 77,863,436 | 59,876,764 | 55,072,348 | 29,917,027 | 33,308,028 | 34,313,435 |
| mem | 42,348,320 | 83,158,473 | 1,538,166 | 128,731,510 | 73,473,478 | 47,270,433 | 2,012,120 | 1,565,253 | 1,037,126 |
| fast-memoize | 33,145,713 | 64,942,152 | 1,349,274 | 190,677,799 | 2,149,467 | 1,999,192 | 1,718,229 | 1,297,911 | 1,031,683 |
| lodash | 25,700,293 | 49,941,573 | 1,459,013 | 67,513,655 | 48,874,559 | 33,436,506 | 1,861,982 | 1,402,532 | 1,112,527 |
| memoizee | 21,546,499 | 27,447,855 | 15,645,143 | 29,701,124 | 27,294,197 | 25,348,244 | 15,359,792 | 15,855,421 | 15,720,217 |
| ramda | 18,804,380 | 35,919,033 | 1,689,727 | 101,557,928 | 1,895,956 | 4,303,215 | 2,305,025 | 1,597,131 | 1,167,025 |
| memoizerific | 6,745,058 | 7,382,030 | 6,108,086 | 8,488,885 | 6,427,832 | 7,229,375 | 5,772,461 | 6,278,344 | 6,273,453 |
| underscore | 6,701,695 | 11,698,265 | 1,705,126 | 18,249,423 | 4,695,658 | 12,149,714 | 2,310,412 | 1,630,769 | 1,174,197 |
| addy-osmani | 4,926,732 | 6,370,152 | 3,483,311 | 12,506,809 | 3,568,399 | 3,035,249 | 6,898,542 | 2,009,089 | 1,542,304 |
# Filesize
`moize` is fairly small (~3.86KB when minified and gzipped), however it provides a large number of configuration options to satisfy a number of edge cases. If filesize is a concern, you may consider using [`micro-memoize`](https://github.com/planttheidea/micro-memoize). This is the memoization library that powers `moize` under-the-hood, and will handle most common use cases at 1/4 the size of `moize`.
# Browser support
- Chrome (all versions)
- Firefox (all versions)
- Edge (all versions)
- Opera 15+
- IE 9+
- Safari 6+
- iOS 8+
- Android 4+
# Development
Standard stuff, clone the repo and `npm install` dependencies. The npm scripts available:
- `benchmark` => run the benchmark suite pitting `moize` against other libraries in common use-cases
- `benchmark:alternative` => run the benchmark suite for alternative forms of caching
- `benchmark:array` => run the benchmark suite for memoized methods using single and multiple `array` parameters
- `benchmark:object` => run the benchmark suite for memoized methods using single and multiple `object` parameters
- `benchmark:primitive` => run the benchmark suite for memoized methods using single and multiple `object` parameters
- `benchmark:react` => run the benchmark suite for memoized React components
- `build` => run rollup to build the distributed files in `dist`
- `clean:dist` => run `rimraf` on the `dist` folder
- `clean:docs` => run `rimraf` on the `docs` folder
- `clean:mjs` => run `rimraf` on the `mjs` folder
- `copy:mjs` => run `clean:mjs` and the `es-to-mjs` script
- `copy:types` => copy internal types to be available for consumer
- `dev` => run webpack dev server to run example app (playground!)
- `dist` => runs `clean:dist` and `build`
- `docs` => runs `clean:docs` and builds the docs via `jsdoc`
- `flow` => runs `flow check` on the files in `src`
- `lint` => runs ESLint against all files in the `src` folder
- `lint:fix` => runs `lint`, fixing any errors if possible
- `test` => run `jest` test functions with `NODE_ENV=test`
- `test:coverage` => run `test` but with code coverage
- `test:watch` => run `test`, but with persistent watcher
- `typecheck` => run `tsc` against source code to validate TypeScript