Skip to main content

useLatestRef

useLatestRef is a hook that keeps a stable ref to the latest committed value, such as a callback or delayed counter.

Import

import { useLatestRef } from '@vortexlabs/vortex';
Current count: 0
No value captured yet
"use client";

import React from "react";
import { Button, Stack, Text, useLatestRef } from "@vortexlabs/vortex";

export const LatestValue = () => {
  const [count, setCount] = React.useState(0);
  const [captured, setCaptured] = React.useState<number | null>(null);
  const [pending, setPending] = React.useState(false);
  const latestCount = useLatestRef(count);
  const timer = React.useRef<ReturnType<typeof setTimeout> | null>(null);

  React.useEffect(
    () => () => {
      if (timer.current !== null) clearTimeout(timer.current);
    },
    [],
  );

  const captureLater = () => {
    setPending(true);
    timer.current = setTimeout(() => {
      setCaptured(latestCount.current);
      setPending(false);
    }, 2000);
  };

  return (
    <Stack gap={3}>
      <Text>Current count: {count}</Text>
      <Stack direction="row" gap={2} wrap>
        <Button onClick={() => setCount(count + 1)}>Increment</Button>
        <Button variant="secondary" onClick={captureLater} disabled={pending}>
          {pending ? "Waiting two seconds…" : "Capture in two seconds"}
        </Button>
      </Stack>
      <Text attributes={{ role: "status" }}>
        {captured === null ? "No value captured yet" : `Captured count: ${captured}`}
      </Text>
    </Stack>
  );
};

Features

  • Keeps the same ref object across renders.
  • Updates .current after a committed value change.
  • Keeps values available to delayed callbacks.

Pass a value and read it through .current. Ref updates do not trigger renders; use state for visible UI.

Keeping a Callback Current

Wrap a changing callback so an existing event listener can read it without subscribing again.

"use client";

import React from "react";
import { useLatestRef } from "@vortexlabs/vortex";

export const useWindowResize = (onResize: () => void) => {
  const callbackRef = useLatestRef(onResize);

  React.useEffect(() => {
    const handleResize = () => callbackRef.current();
    window.addEventListener("resize", handleResize);
    return () => window.removeEventListener("resize", handleResize);
  }, [callbackRef]);
};

The first value is available immediately. Later values update after React commits, so .current can still hold the previous value during render.

Keep subscription targets in effect dependencies. A ref keeps a callback current; it does not replace cleanup when the target changes.