use-stable-reference
v1.1.2
Published
Simple React hooks to access referentially stable, up-to-date versions of non-primitives.
Downloads
14
Maintainers
Readme
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:
useEvent
RFC, legacy React docs- Basic implementation
wouter
- Initializing the
useRef
to the callback argument, rather thannull
- Initializing the
react-use-event-hook
- Support passing a callback argument that uses the
this
keyword
- Support passing a callback argument that uses the
react-use/useLatest
- Updating the
ref
in 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
ref
is updated - The DOM updates are discarded because a second, higher-priority render was triggered
- The higher-priority render occurs
- Any code which uses the
ref
value before it's updated in step6
is using a value from a render that was discarded! - The
ref
is 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
useStableCallback
anduseStableValue
may 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!