A tiny library that memoizes invocations, not functions — so you can memoize calls to functions you don't own.
Works similarly to React's useMemo, but for any JavaScript/TypeScript function, anywhere in your code.
Traditional memoization wraps a function to cache its results. But what if you don't own the function? What if it's from a library, or you need different caching strategies for different call sites?
re-call memoizes the invocation itself, not the function. This means you can:
- ✅ Memoize third-party functions without wrapping them
- ✅ Use different caching strategies for the same function in different places
- ✅ Cache method calls with proper
thiscontext - ✅ Choose between reference equality, shallow equality, or deep equality comparison
npm install @chamie/re-callimport reCall from '@chamie/re-call';
const expensiveCalculation = (a: number, b: number) => {
console.log('Computing...');
return a + b;
}
reCall(expensiveCalculation, [1, 2]); // Logs: "Computing...", returns 3
reCall(expensiveCalculation, [1, 2]); // Returns 3 (from cache, no log)
reCall(expensiveCalculation, [2, 3]); // Logs: "Computing...", returns 5Caches based on shallow equality. Compares array length and each element with ===.
import { shallow } from '@chamie/re-call';
// or
import shallow from '@chamie/re-call';
shallow(testFn, [1, 2]); // Function executed
shallow(testFn, [1, 2]); // Result from cache ✓
shallow(testFn, [2, 3]); // Function executed
shallow(testFn, [1, [1, 2]]); // Function executed
shallow(testFn, [1, [1, 2]]); // Function executed (nested array is different reference)Caches based on reference equality (Object.is). Only returns cached result if the exact same parameter array is passed.
import { byRef } from '@chamie/re-call';
const testFn = (a: number, b: number) => {
console.log('Function executed');
return `${a} + ${b}`;
}
byRef(testFn, [1, 2]); // Function executed
byRef(testFn, [1, 2]); // Function executed (new array reference)
const array = [1, 2];
byRef(testFn, array); // Function executed
byRef(testFn, array); // Result from cache ✓Caches based on deep equality. Recursively compares nested objects and arrays.
import { deep } from '@chamie/re-call';
deep(testFn, [1, 2]); // Function executed
deep(testFn, [1, 2]); // Result from cache ✓
deep(testFn, [1, [1, 2]]); // Function executed
deep(testFn, [1, [1, 2]]); // Result from cache ✓ (deep equality works!)All memoization functions support zero-parameter functions:
const getData = () => {
console.log('Fetching data...');
return fetch('/api/data');
}
deep(getData); // Fetching data...
deep(getData); // Result from cache ✓Use a custom comparison function for fine-grained control:
import { byRef } from '@chamie/re-call';
const comparator = (a, b) => a[0].id === b[0].id;
byRef(testFn, [{ id: 1, name: 'Alice' }], { comparator });
byRef(testFn, [{ id: 1, name: 'Bob' }], { comparator }); // Cache hit! (same id)Memoize method calls with proper context:
class DataFetcher {
apiKey: string;
constructor(apiKey: string) {
this.apiKey = apiKey;
}
fetchData(endpoint: string) {
console.log(`Fetching from ${endpoint} with key ${this.apiKey}`);
return fetch(`https://api.example.com/${endpoint}`, {
headers: { 'Authorization': this.apiKey }
});
}
onClick() {
// Memoize with proper 'this' context
shallow(this.fetchData, ['/users'], { this: this });
}
}Create memoized versions of functions:
import { wrap, wrapShallow, wrapDeep } from '@chamie/re-call';
const memoizedFn = wrapShallow(expensiveCalculation);
memoizedFn(1, 2); // Computing...
memoizedFn(1, 2); // Cached ✓Check if a function has cached results:
import { hasCache } from '@chamie/re-call';
shallow(testFn, [1, 2]);
hasCache(testFn); // trueGet the cached result without invoking the function:
import { peek } from '@chamie/re-call';
shallow(testFn, [1, 2]); // "1 + 2"
peek(testFn); // "1 + 2"Clear the cache for a specific function:
import { clearOne } from '@chamie/re-call';
shallow(testFn, [1, 2]);
hasCache(testFn); // true
clearOne(testFn);
hasCache(testFn); // false
peek(testFn); // undefinedClear all cached results:
import { clearAll } from '@chamie/re-call';
clearAll(); // Clears everythingimport { shallow } from '@chamie/re-call';
const uploadFile = (file: Blob, upload: (file: Blob) => string) => {
if (!confirm(`Uploading file!`)) return;
// Only upload if this exact file hasn't been uploaded yet
const serverId = shallow(upload, [file]);
return serverId;
}import reCall from '@chamie/re-call';
const fetchUser = (userId: string) => {
console.log(`Fetching user ${userId}...`);
return fetch(`/api/users/${userId}`).then(r => r.json());
}
// In your React component or elsewhere
const user = await reCall(fetchUser, [userId]);
// Subsequent calls with same userId return cached databutton.addEventListener('click', () => {
// Won't refetch if already cached
shallow(dataFetcher.fetchData, ['/users'], { this: dataFetcher });
});re-call uses WeakMap internally, making it garbage-collection friendly:
- ✅ When a function is no longer referenced, its cache is automatically cleaned up
- ✅ No memory leaks from cached results
- ✅ Safe to use with temporary functions, closures, and dynamic code
- ✅ No manual cleanup required (though
clearOneandclearAllare available if needed)
This means you can safely memoize functions without worrying about memory accumulation:
// These caches will be garbage collected when the functions are no longer referenced
[1, 2, 3].forEach(n => {
const fn = () => expensiveCalc(n);
deep(fn); // Safe! Cache will be GC'd with the function
});If you're familiar with React's useMemo, re-call works similarly but without React:
// React
const result = useMemo(() => expensiveCalc(a, b), [a, b]);
// re-call
const result = shallow(expensiveCalc, [a, b]);Key differences:
- ✅ Works anywhere, not just in React components
- ✅ Can memoize functions you don't own
- ✅ Choose comparison strategy per call site
- ✅ Persistent cache across renders (not tied to component lifecycle)
Fully typed with TypeScript! All functions preserve parameter and return types:
const add = (a: number, b: number): number => a + b;
const result = shallow(add, [1, 2]); // result: number
// shallow(add, ["1", "2"]); // ❌ Type error!MIT