Skip to content

fix(android): use unstable_getBoundingClientRect for synchronous layout measurement on New Architecture#2292

Open
wildseansy wants to merge 1 commit into
Shopify:mainfrom
wildseansy:fix/android-new-arch-async-measure-layout
Open

fix(android): use unstable_getBoundingClientRect for synchronous layout measurement on New Architecture#2292
wildseansy wants to merge 1 commit into
Shopify:mainfrom
wildseansy:fix/android-new-arch-async-measure-layout

Conversation

@wildseansy
Copy link
Copy Markdown

Description

Fixes FlashList rendering nothing on Android New Architecture (RN 0.78+).

Root cause

View.measureLayout() on Android New Architecture invokes its callback asynchronously — it returns before the callback fires. measureLayout() in measureLayout.ts calls it, then immediately returns the local layout object, which is still {x:0, y:0, width:0, height:0}.

This means measureParentSize() always returns {width:0, height:0}, so recyclerViewManager.updateLayoutParams() is called with a window height of 0. FlashList never renders any items.

The onLayout event detects that the stored container size (containerViewSizeRef) is 0 and calls recyclerViewContext.layout() to trigger a re-render — but useLayoutEffect runs measureLayout again with the same async/0 result, creating an infinite re-render loop that never resolves.

Fix

unstable_getBoundingClientRect() reads directly from the Fabric shadow tree and is synchronous on New Architecture (RN 0.78+). Use it in measureLayout when available, and fall back to measureLayoutRelative on Old Architecture where the method doesn't exist.

The commented-out code at the top of measureLayout() pointed at this same API. This PR restores that intent with proper type safety and an Old Arch fallback.

Relationship to #2269

PR #2269 adds an early return when the container measures 0x0 (a different scenario: transient zero measurements during navigation stack transitions on web). Our fix addresses a separate root cause: the measurement itself never returns a non-zero value on Android New Arch. These two fixes are complementary.

Testing

Verified on Android with React Native 0.85 + New Architecture enabled. FlashList renders items correctly after this change. Without it, the list is permanently blank.

Checklist

  • Tested on Android New Architecture (RN 0.85)
  • Falls back to measureLayoutRelative on Old Architecture
  • TypeScript: unstable_getBoundingClientRect is not on the public View type — added a local ViewWithBoundingClientRect type cast

…ut measurement

On Android New Architecture (RN 0.78+), View.measureLayout() invokes its
callback asynchronously. measureLayout() calls it synchronously and returns
the local layout object before the callback fires, so the result is always
{x:0, y:0, width:0, height:0}.

This causes FlashList to initialize with a window size of 0, so no items
are ever rendered. The onLayout handler detects the 0 and calls layout(),
triggering a re-render — but useLayoutEffect runs measureLayout again with
the same async/0 result, creating an infinite re-render loop that never
resolves.

unstable_getBoundingClientRect() reads directly from the Fabric shadow
tree and is synchronous on New Architecture. Use it in measureLayout when
available, falling back to measureLayoutRelative for Old Architecture where
the method does not exist.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant