use-memo-one
use-memo-one is a tiny pair of React hooks that mirrors useMemo and useCallback while promising to keep the last cached reference until its component is garbage-collected. The built-in React hooks are performance hints whose caches React may discard; useMemoOne and useCallbackOne make reference stability part of their contract as long as every dependency remains strictly equal. That narrow guarantee can help code where object or callback identity is observable, but it uses more memory and does not make missing dependencies safe.
A focused compatibility tool for React 16.8 through 18 when stable cache identity is genuinely semantic. Most new code should use React's built-ins, and React 19 projects should not add a package whose declared peer support and maintenance stopped at React 18.
Use it if
- You need a memoized object or callback to retain the same reference while its dependency list is unchanged
- A third-party component or subscription treats reference identity as behavior, not just a rendering optimization
- You maintain a React 16.8 through 18 application and want an API that can replace useMemo or useCallback with minimal code changes
- You want zero runtime dependencies beyond the required React peer
- You are on React 19: version 1.1.3 declares a peer range of React 16.8, 17, or 18, so modern package managers can report an unsupported peer or reject installation under strict peer rules
- Memoization is only a performance optimization in your component: React's built-in useMemo and useCallback require no extra package and are the APIs React itself documents
- You need active maintenance: npm 1.1.3 was published in August 2022, the repository was last pushed in December 2022, and its most recent functional compatibility change only added React 18 to the peer range
- Memory retention is a concern in a large mounted tree: the README explicitly says its stable guarantee consumes more memory because React cannot release this cache while the component remains reachable
- You expect deep dependency comparison or automatic stale-closure protection: the source compares array entries with strict inequality, so newly created objects trigger recomputation and omitted values can leave callbacks stale
Setup reality
Installation is one npm command and the package has no runtime dependencies, but peer compatibility is the first surprise. Version 1.1.3 accepts react ^16.8.0, ^17.0.0, or ^18.0.0; React 19 is outside that declared range, so npm may warn and stricter installers may stop. The package ships CommonJS and ESM builds plus its own index.d.ts, so React 16.8 through 18 projects need no compiler plugin or type package. There are four named exports: useMemoOne, useCallbackOne, and drop-in aliases named useMemo and useCallback. The aliases make eslint-plugin-react-hooks recognize dependency arrays, but they collide if the same file also imports those names from react. The dependency check is shallow and positional, using strict equality for each item. An inline object, array, or function in the dependency list changes on every render unless stabilized first. Omitting the dependency list does not mean cache forever; the repository tests confirm that no list recomputes on every render. An empty list retains the first value, which also means a callback can capture initial props forever. Factories still run during rendering and must be pure, especially under React development checks. Finally, this guarantee costs memory: the README says the last cache is retained until the component can be garbage-collected, rather than being releasable by React.
Patterns
Keep a derived object reference stablememoize-derived-object
import { useMemoOne } from 'use-memo-one';
function Profile({ name, role }) {
const summary = useMemoOne(() => ({ name, role }), [name, role]);
return <ProfileCard summary={summary} />;
}The same object is returned while both dependencies are strictly equal; a change to either creates a new object.
Keep a callback stable until its input changesmemoize-callback
import { useCallbackOne } from 'use-memo-one';
function SaveButton({ documentId, save }) {
const onSave = useCallbackOne(() => save(documentId), [save, documentId]);
return <button onClick={onSave}>Save</button>;
}List every captured value. Stable identity does not prevent a stale closure when a dependency is omitted.
Use aliases recognized by hooks lintinguse-lint-friendly-aliases
import { useMemo, useCallback } from 'use-memo-one';
const options = useMemo(() => ({ roomId }), [roomId]);
const connect = useCallback(() => openRoom(roomId), [roomId]);Do not also import useMemo or useCallback from react in this file; the README warns that the names collide.
Stabilize a context provider valuememoize-context-value
import { useMemoOne } from 'use-memo-one';
function SessionProvider({ user, signOut, children }) {
const value = useMemoOne(() => ({ user, signOut }), [user, signOut]);
return <SessionContext.Provider value={value}>{children}</SessionContext.Provider>;
}This only prevents identity changes when user and signOut are stable; it does not stop consumers rendering when either really changes.
Pass a stable prop to a memoized childsupport-memoized-child
import { memo } from 'react';
import { useMemoOne } from 'use-memo-one';
const Chart = memo(({ config }) => <ChartCanvas config={config} />);
function Dashboard({ theme, limit }) {
const config = useMemoOne(() => ({ theme, limit }), [theme, limit]);
return <Chart config={config} />;
}Memoizing the prop helps only if Chart is memoized or otherwise observes reference identity.
Avoid reconnecting an effect for an equivalent options objectstabilize-effect-dependency
import { useEffect } from 'react';
import { useMemoOne } from 'use-memo-one';
function Room({ roomId, serverUrl }) {
const options = useMemoOne(() => ({ roomId, serverUrl }), [roomId, serverUrl]);
useEffect(() => {
const connection = connect(options);
return () => connection.close();
}, [options]);
}Prefer creating simple effect-only objects inside the effect when possible; this pattern is for code that also needs the stable object elsewhere.
Create one value for a mounted componentcache-for-component-lifetime
import { useMemoOne } from 'use-memo-one';
function Editor() {
const model = useMemoOne(() => createEditorModel(), []);
return <EditorView model={model} />;
}An empty dependency list retains the initial result while mounted. The factory must be pure because it runs during rendering and development checks may invoke rendering more than once.
Understand an omitted dependency listrecompute-every-render
import { useMemoOne } from 'use-memo-one';
function Clock() {
const snapshot = useMemoOne(() => ({ renderedAt: Date.now() }));
return <time>{snapshot.renderedAt}</time>;
}Repository tests confirm that omitting the second argument does not memoize across renders. Pass [] only when retaining the first result is intentional.
Depend on primitives instead of a new objectavoid-unstable-dependencies
// Bad: filter is new on every render
const filter = { status, owner };
const rows = useMemoOne(() => selectRows(data, filter), [data, filter]);
// Better: build it inside the factory
const rows = useMemoOne(
() => selectRows(data, { status, owner }),
[data, status, owner],
);The source uses strict equality for each dependency. It does not perform deep or structural comparison.
Preserve callback parameter types in TypeScripttype-callback-parameters
import { useCallbackOne } from 'use-memo-one';
function Search({ runSearch }: { runSearch: (query: string) => void }) {
const submit = useCallbackOne((query: string) => {
runSearch(query.trim());
}, [runSearch]);
return <SearchBox onSubmit={submit} />;
}The bundled declaration keeps the callback's function type, but React 19 remains outside the package's declared peer range.
Alternatives
| Package | Registry | Pick it when |
|---|---|---|
| react | npm | Use built-in useMemo and useCallback when cache identity is only a performance optimization or when the app is on React 19 |
| memoize-one | npm | Memoize the latest call of a regular function outside React's hook lifecycle |
| use-callback-ref | npm | Keep a callable reference stable while updating the function it invokes, which is a better fit for latest-value event callbacks |