use-stable-reference v1.1.2
use-stable-reference
Simple React hooks to access referentially stable, up-to-date versions of non-primitives.
Basic usage
import { useStableCallback, useStableValue } from "use-stable-reference";
function Library({ unstableCallback, unstableValue }) {
const stableCallback = useStableCallback(unstableCallback);
const getStableValue = useStableValue(unstableValue);
useEffect(() => {
if (/* ... */) {
stableCallback()
const stableValue = getStableValue()
}
// safe to add to dependency arrays!
}, [stableCallback, getStableValue, /* ... */]);
}use-stable-reference really shines for library authors or for those writing reusable code. With a library-consumer relationship, the library author can't reasonably expect that the consumer will preemptively wrap any callbacks in a useCallback, or any referentially unstable values in a useMemo. This leaves the author with a few possible choices for how to handle consumer-provided non-primitive arguments:
- Leave them out of any dependency arrays, and ignore any eslint React linter warnings/errors
- Leave them in the dependency arrays, expecting that the effects / memoizations will run every render
- Wrap them in a
useStableCallback/useStableValue
With option 3, the returned callback/value-getter are referentially stable, can safely be used in dependency arrays, and are guaranteed to always be up-to-date if the underlying option ever changes! 🎉
API
useStableCallback
useStableCallback accepts one argument, a callback of type: (...args: any[]) => any
useStableCallback returns an up-to-date, referentially stable callback.
useStableValue
useStableValue accepts one argument, a value of type: unknown
useStableValue returns a referentially stable callback that returns an up-to-date copy of the argument.
FAQ
Haven't I seen this before?
A version of this hook has been floating around the React community for a while, often referred to as useEvent or useEffectCallback. This package hopes to distill the best aspects of several different implementations:
useEventRFC, legacy React docs- Basic implementation
wouter- Initializing the
useRefto the callback argument, rather thannull
- Initializing the
react-use-event-hook- Support passing a callback argument that uses the
thiskeyword
- Support passing a callback argument that uses the
react-use/useLatest- Updating the
refin the render method - This method is controversial, but I think the trade-offs are worth it; see below.
- Updating the
Isn't updating a ref in the render method a bad practice?
Updating a ref in the render method is only dangerous when using concurrent features. Consider the following scenario:
- A component re-renders, i.e. the render method runs
- The
refis updated - The DOM updates are discarded because a second, higher-priority render was triggered
- The higher-priority render occurs
- Any code which uses the
refvalue before it's updated in step6is using a value from a render that was discarded! - The
refis updated to the intended value
Thankfully, this is rarely something we need to worry about, for a few reasons:
- Concurrent mode is opt-in, triggered only when using concurrent features
- Concurrent features are only available in React 18+
- The React compiler, which will make this library unnecessary, is in beta starting with React 19
- The callbacks and values that are passed to
useStableCallbackanduseStableValuemay be referentially unstable, but generally have the same behavior from render to render
In other words, for developers using React < 18, there's no issue because concurrent features aren't available; for devs using React > 19, you shouldn't need this package at all because of the React compiler; for those stuck in the middle using React 18, there's a good chance all the ref values will have the same behavior anyway, as long as you pass a callback/value with the same behavior every render.
That leaves just one scenario to consider. For devs using React 18, with concurrent features, with dynamic callbacks/values, consider yourselves warned: your refs may be out-of-sync with your render!