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
testIDis 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¶
- Write a Babel plugin that visits every JSX opening element.
- If the element has a
testIDprop and does not already have an explicitcollapsableprop, injectcollapsable={false}into its props. - 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
testIDwithoutcollapsable={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 ofrefusage is less clean thantestID). - An ESLint rule warning on views that have
refbut notcollapsable={false}. - Refactor to use
collapsable={false}explicitly in the affected components (as Shopify did per-call-site for theActionSheetIOScases).
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.
Related¶
- systems/react-native — framework context.
- concepts/view-flattening — the optimization that creates the need for this pattern.
- companies/shopify — origin of the canonical instance.