Skip to content

reze: create the avatar image element once so a sibling fallback does not recreate it - #13

Merged
knst0 merged 2 commits into
knst0:mainfrom
umzi2:fix/reze-avatar-image-recreate
Oct 24, 2026
Merged

knst0 merged 2 commits into
knst0:mainfrom
umzi2:fix/reze-avatar-image-recreate

Conversation

@umzi2

@umzi2 umzi2 commented Oct 24, 2026

Copy link
Copy Markdown
Contributor

Problem

In @rebase-ui/reze, an Avatar.Image placed next to an Avatar.Fallback is rebuilt every time the loading status changes. When the browser cache is bypassed (DevTools "Disable cache", Cache-Control: no-store responses), each new <img> is not complete right away, so the status goes back to loading, the image loads, the status becomes loaded, and the <img> is rebuilt again. The result is an endless loop: about 700 load events per second on a single avatar in a Firefox profile, a frozen tab that ignores reload.

With the cache enabled a rebuilt <img> comes from the memory cache already complete, so the status does not flip back and the loop never starts. That is why it goes unnoticed in normal use.

Cause

Avatar.Root renders its children as an array. Reze inserts an array that contains function slots with one shared effect: whenever any slot's dependencies change, every slot function is called again. Avatar.Fallback reads the loading status inside its slot, so each status change re-runs the slots of all siblings. The slot returned by Avatar.Image is created by dynamicElement, and calling it again runs createComponent again, which creates a new <img>.

Fix

AvatarImage now returns a slot that creates its element on the first call and returns the same element on later calls. The element is built lazily inside the slot, because creating it in the component body would write the status store during a render binding, which Reze rejects.

One side effect: changing the as prop of Avatar.Image after the first render no longer swaps the element. Avatar.Root and Avatar.Fallback still react to as as before.

Verification

  • New unit test: after a load event the image next to a fallback is the same element. It fails on main and passes with the fix.
  • Whole @rebase-ui/reze project: 1542 tests pass, tsc --noEmit is clean.
  • Real browser (Waterfox headless, cache disabled) with Avatar.Root + Avatar.Image + Avatar.Fallback, counting setAttribute calls (the counter stops at 400):
    • before: 401 calls, the loop runs;
    • after: 3 calls, 1 load event, 1 <img>; the same 3 calls with the cache enabled.
  • @rebase-ui/solid is not affected. The same test (image identity after load next to a fallback) passes in Chromium on solid; its component body runs once and the element is created once, so I did not add it there.

A changeset (@rebase-ui/reze patch) is included.

@umzi2
umzi2 requested a review from knst0 as a code owner October 24, 2026 13:01
@knst0
knst0 merged commit d2f7a8e into knst0:main Oct 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants