Skip to content

PATTERN Cited by 1 source

Babel plugin for automatic collapsable=false

Pattern

When a rendering optimization (in React Native's new architecture: view flattening) silently breaks a pervasive concern (end-to-end tests' ability to find views by testID), and the fix is a single prop applied to every affected call site — build a Babel plugin that injects the prop at compile time on every component matching the affected shape. Eliminate the human error of forgetting the fix at specific call sites.

The canonical wiki instance is Shopify's 2025 React Native new- architecture migration (see sources/2025-09-12-shopify-migrating-to-react-natives-new-architecture).

The problem shape

  • concepts/view-flattening — the new architecture's Fabric renderer omits views from the native tree when they have no visual effect.
  • A view with a testID (used by Appium, Detox, XCUITest for end-to-end element location) but no other visual properties is a flattening candidate.
  • If flattened, the testID is gone from the native tree. Tests fail to find elements.
  • The manual fix — collapsable={false} — must be set on every <View testID="..."> in the codebase. A large React Native app has thousands of test IDs.

Solution shape

  1. Write a Babel plugin that visits every JSX opening element.
  2. If the element has a testID prop and does not already have an explicit collapsable prop, inject collapsable={false} into its props.
  3. Apply the plugin in the build pipeline for every module that might render test IDs.

Shopify's description: "To address this we created a Babel plugin to automatically set collapsable={false} to components with an explicit test identifier."

Why Babel-plugin beats alternatives

  • vs. manual per-call-site fixes — thousands of call sites, infinite forgetting opportunities. Non-scalable.
  • vs. ESLint rule — ESLint catches missing props; it doesn't inject them. An ESLint rule is a good companion (catches testID without collapsable={false} in PR review), but it still relies on humans writing the fix.
  • vs. runtime prop injection — would require a wrapper component. Forces a codebase-wide find/replace to adopt the wrapper, which is precisely the kind of change the Babel plugin is trying to avoid.
  • vs. disabling view flattening globally — gives up the rendering win. View flattening materially cuts native view count in typical React component trees; global disable is a perf regression.

Limitations

The plugin only handles the testID case. The other view-flattening failure mode — <View ref={ref}> — requires a different fix, typically either:

  • A second Babel-plugin rule targeting ref (feasible but trickier — static analysis of ref usage is less clean than testID).
  • An ESLint rule warning on views that have ref but not collapsable={false}.
  • Refactor to use collapsable={false} explicitly in the affected components (as Shopify did per-call-site for the ActionSheetIOS cases).

Broader principle

"Make the compiler fix the pervasive concern; let humans handle the targeted cases." A Babel plugin is cheap, acts at every call site uniformly, and eliminates an entire class of bug. It's the canonical "don't write documentation asking developers to remember to do X — make the toolchain do X" move.

Last updated · 470 distilled / 1,213 read