Skip to content

useRefState ​

A hybrid React hook that combines useState and useRef β€” giving you both reactive state updates and synchronous access to the latest value.


πŸ“˜ Overview ​

React’s useState updates are asynchronous, making it tricky to access the most recent value in certain callbacks or effects (like inside setInterval or event listeners).
useRefState solves this problem by coupling state with a ref that always stays up to date β€” even between renders.

This hook is especially useful for real-time data, interval logic, or asynchronous event handling.


πŸš€ Features ​

  • βœ… Provides reactive state and immediate ref access
  • βš™οΈ Synchronous ref.current updates
  • πŸ”„ Perfect for timers, listeners, and async operations
  • 🧩 Drop-in replacement for useState
  • ⚑ Type-safe and dependency-free

πŸ’‘ Example ​

tsx
import { useEffect } from "react";
import { useRefState } from "react-hookstack";

export default function RefStateExample() {
  const [count, setCount, countRef] = useRefState(0);

  // Log the latest count every second, even if re-renders are delayed
  useEffect(() => {
    const interval = setInterval(() => {
      console.log("Current count (sync ref):", countRef.current);
    }, 1000);
    return () => clearInterval(interval);
  }, []);

  return (
    <div>
      <h3>Count: {count}</h3>
      <button onClick={() => setCount(c => c + 1)}>Increment</button>
    </div>
  );
}

🧠 Why it matters ​

Inside an interval or event listener, countRef.current always gives the latest value β€” even if the component hasn’t re-rendered yet.

🧩 API Reference ​

Signature ​

tsx
function useRefState<T>(
  initialValue: T
): [T, (value: T | ((prev: T) => T)) => void, React.MutableRefObject<T>];

Parameters ​

NameTypeDescription
initialValueTThe initial state value.

Returns ​

IndexNameTypeDescription
[0]stateTThe current state value (reactive, triggers re-renders).
[1]setState(value: T | ((prev: T) => T)) => voidUpdates the state and keeps the ref synchronized.
[2]refReact.MutableRefObject<T>Always points to the latest state value (non-reactive).

🧠 When to Use ​

  • When you need instant access to the latest state value
  • When using setInterval, event listeners, or async callbacks
  • To avoid stale closures in functional updates
  • To simplify state tracking in non-reactive contexts

usePrevious β€” Retrieve the previous value after a render.

usePreviousDistinct β€” Get only distinct previous values.

useBoolean β€” Manage boolean state with handy toggles.

βš™οΈ Implementation ​

tsx
import { useCallback, useRef, useState } from "react";

/**
 * Combines useState and useRef to provide synchronous and reactive state access.
 */
export function useRefState<T>(
  initialValue: T
): [T, (value: T | ((prev: T) => T)) => void, React.MutableRefObject<T>] {
  const [state, setState] = useState<T>(initialValue);
  const ref = useRef<T>(state);

  const setValue = useCallback((value: T | ((prev: T) => T)) => {
    setState((prev) => {
      const newValue =
        typeof value === "function" ? (value as (prev: T) => T)(prev) : value;
      ref.current = newValue;
      return newValue;
    });
  }, []);

  // Keep ref in sync
  ref.current = state;

  return [state, setValue, ref];
}