useValue()

A React hook for persisting non-stateful values

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.

js
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:

jsx
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 memood).

js
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:

js
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:

js
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.

Code Playground
Let your squad in on the awesomeness.

A newsletter for front-end web developers

Stay up-to-date with my latest articles, experiments, tools, and much more!

Issued monthly (or so). No spam. Unsubscribe any time.