Skip to content

v6.5.5

Latest

Choose a tag to compare

@childrentime childrentime released this 20 Aug 09:39
· 4 commits to main since this release

Follow-up to v6.5.4. The useScrollLock bug there had a root cause the fix didn't address: an effect that depends on a target which is a new function on every render. This release audits every hook that accepts a BasicTarget and fixes the three that had it — in two of them the consequence is worse than it was in useScrollLock.

🐞 Bug Fixes

useInfiniteScroll: no longer fires onLoadMore on every render

The load-more effect listed target in its dependencies. A getter target — () => element, one of the documented BasicTarget forms — is a new function every render, so the effect re-ran every render and called onLoadMore each time:

// three renders → three loads; loading appends data → renders → loads again…
useInfiniteScroll(() => containerEl, loadMore);

Measured: 3 renders, 3 loads with a getter target, 0 with a ref. Because loading more appends to state and renders again, it never settled. The documented demo passes a ref, which is why the loop went unnoticed.

useCssVar: no longer rebuilds its observer and its setter every render

set and updateCssVar were memoised on [target, prop], and the effect that installs the MutationObserver depended on both plus target. A getter target invalidated all of them every render, so with observe: true:

  • the observer was disconnected and reconstructed on every render — 4 observers across 3 renders, against 1 for a stable element — leaving a gap where mutations go unseen
  • the set returned to callers changed identity every render, invalidating any consumer memo built on it

set is now stable for the lifetime of the hook, and the observer is built once per element.

useScrollLock: the effect is keyed on the element, not the getter

v6.5.4 made the effect idempotent, but it still re-ran on every render with the recommended () => document.body getter, re-writing overflow: hidden each time and leaving a trap for anything added to that effect later. The element-keyed snapshot from 6.5.4 stays as the invariant that guarantees one read per lock.

🔍 Audit

All three now resolve their target through useStableTarget, the utility useEventListener, useIntersectionObserver, useMutationObserver, useResizeObserver and useSticky already used. Those five were already correct; these three were the only hooks depending on a raw target. Every other hook that takes a target reaches the DOM through useEventListener, useResizeObserver or useEvent, all of which are already stable.

✅ Tests

378 tests, up from 358. useInfiniteScroll gains its first spec file (9 tests — no load on mount, loading on arrival at the bottom edge with both ref and getter targets, no load on unrelated re-renders with either, and preserveScrollPosition including its ordering against an async onLoadMore); useCssVar grows from a stub to 11; useScrollLock to 13. Five of the new tests fail on 6.5.4.

Full changelog: v6.5.4...v6.5.5