This hook lets you persist a value across renders in a functional component, without using a state. This is useful in cases where you want to be able to update a value without triggering a rerender, and when you want to be able to get the most current value at any given moment.
import { useRef, useCallback } from 'react';
export function useValue(content) {
const ref = useRef();
ref.current = content; // Always update the ref to the latest value
return {
get: useCallback(() => ref.current, []),
set: useCallback(setter => {ref.current = setter(ref.current)}, [])
};
}
You can use it like so:
const MyComponent = () => {
const { get, set } = useValue(value);
// Read
const value = get();
// Write
set(value => value);
};
One use case for this is to create callbacks whose reference never changes, yet always have access to the latest external values. That way you can avoid redundant renders and attaching/reattaching handlers.
In the example below, a component that takes handler
as a prop won't re-render when value
changes (provided that
it's properly memo
od).
let value = 'whatever'; // Assume this value changes over time
const { get } = useValue(value);
const handler = useCallback(() => {
const value = get();
}, [get]); // Since "get" never changes, the callback reference will never change either
In the following example, useEffect
will remove & reattach the event listener every time value
changes:
let value = 'whatever'; // Assume this value changes over time
const handler = useCallback(() => {
// Do something with 'value'
}, [value]);
useEffect(() => {
document.addEventListener('resize', handler));
return () => document.removeEventListener('resize', handler));
}, [handler]);
However, you can avoid that with our useValue()
hook:
let value = 'whatever'; // Assume this value changes over time
const { get } = useValue(value);
const handler = useCallback(() => {
const value = get();
// Do something with 'value'
}, [get]);
// The event handler will only be attached once since 'handler' will never change
useEffect(() => {
document.addEventListener('resize', handler));
return () => document.removeEventListener('resize', handler));
}, [handler]);
I use this hook extensively in many of my other hooks, as a means to avoid these redundant re-binding cycles.
#Example
In the example below, clicking on "Increment" will increment the counter, but the component won't re-render. Clicking on "log value to console" will log the current value to the console.
Notice that handleOnClick
does not have a dependency on the actual value, so it will only be generated once.
This is useful when passing callbacks to child components, so that they don't re-render when the value changes.