import { type Accessor, createRenderEffect, untrack } from "solid-js"; /** * Plain `{ current }` container matching the shape other `MutableRef` * declarations in this codebase use (see `floating-ui-react/types.ts`, * `field-context.ts`, `scroll-area-root-context.ts`). Kept structurally * identical — TypeScript duck-types them, so a `MutableRef` from any * module is interchangeable at call sites. */ export interface MutableRef { current: T; } /** * Port of Base UI React's `useValueAsRef` / "latest ref" idiom. Returns a * stable-identity container whose `.current` is kept in sync with the latest * value produced by `value()`. * * React uses `useRef(value)` plus a layout effect to flush the new value into * `ref.current` before the browser paints. The Solid equivalent is a * `createRenderEffect` — it runs in the render phase, synchronously, before * paint, which matches React's layout-effect timing closely enough for the * "latest value" contract. * * Typical usage: a callback captured in a non-reactive scope (event listener, * timer, Promise `.then`) needs to read the current value at firing time * without causing the captured scope to re-run when the value changes. Read * `ref.current` inside the callback; the consumer gets the newest value * without any reactivity propagation. * * The returned object's identity is stable for the component's lifetime — it * is allocated once and only `.current` is reassigned. This mirrors React's * guarantee that `useRef`'s wrapper object doesn't change. * * @param value Accessor for the tracked value. * @returns A mutable ref whose `.current` reflects the latest `value()`. */ export function useLatestRef(value: Accessor): MutableRef { // Seed `.current` with the initial value so reads that happen before the // first render-effect apply (e.g. synchronous post-mount code inside the // same setup phase) still see a real value, not `undefined`. The seed // is intentionally a snapshot — subscription happens through the // `createRenderEffect` below — so wrap in `untrack` to silence // `[STRICT_READ_UNTRACKED]` raised in the consumer's component-body // strict-read scope. const ref: MutableRef = { current: untrack(value) }; // Keep `.current` pinned to the latest tracked value. `createRenderEffect` // runs synchronously in the render phase (before paint), so downstream // layout-effect reads and DOM event handlers will observe the freshest // value. Solid 2's render effect uses the split compute/apply shape — the // compute half tracks `value()` and forwards it to the apply half, which // writes it into the ref. No `prev` comparison here: we want every tick // through, even when `Object.is` considers the new value equal (the // writes are idempotent). createRenderEffect( () => value(), (next) => { ref.current = next; }, ); return ref; }