Which React Native components render but behave differently under React Native Web, and why can web-tested code still fail on React Native 0.87?
answer
- renders is not the same as works
- a red outline in development
- no pull-to-refresh, no keyboard frame
- no native driver in the browser
- the web port keeps APIs core removed
basics
~20 sSome components are stubs on the web: TouchableNativeFeedback is an unimplemented view, RefreshControl a plain View, KeyboardAvoidingView a mock. React Native Web also still exports InteractionManager, which React Native 0.87 removed, so web-only testing hides native failures.
solid answer
~40 sIn React Native Web some components exist only as placeholders. `TouchableNativeFeedback` renders an unimplemented view (a plain `View`, outlined in red with `alignSelf: 'flex-start'` in development) that ignores presses; `RefreshControl` renders a plain `View`, so there is no pull-to-refresh; `KeyboardAvoidingView` and `StatusBar` are mocks. Others are partial: `ScrollView` has no momentum scroll events, `Text` has no `onLongPress`, `TextInput` does not auto-grow, `Image` lacks multiple sources and request headers, and `Animated` falls back to JavaScript with a warning when `useNativeDriver` is `true`. The reverse gap matters too: React Native Web still exports `InteractionManager`, `CheckBox` and `Picker`, but React Native 0.87 core removed or never had them; its development build throws on `InteractionManager` access. The library treats APIs React Native deprecated as unsupported, so shared code should target current React Native.
go deeper
Recall that some components are only placeholders on the web, for example TouchableNativeFeedback and RefreshControl.
List the stubs, the partial components and the layout differences, and explain the development-only red outline on unimplemented views.
Explain the reverse gap, where the web port still exports APIs React Native 0.87 removed, and how QA and lint rules keep shared code on current APIs.
Decide how much platform-specific QA the web target receives and which component set the design system allows, so gaps are designed out rather than discovered.
## Rendering is not the same as working **React Native Web** exports almost every component name React Native has, so shared code usually compiles and renders. That hides the gaps: a component can appear on screen and still not do what it does on a phone. The library's own compatibility table marks each component as supported, mocked, partial or not started. ## Stubs and mocks | Component | On the web | |---|---| | `TouchableNativeFeedback` | an unimplemented view: a plain `View` that ignores `onPress`, drawn with a 1-pixel red border and `alignSelf: 'flex-start'` in development only | | `RefreshControl` | a plain `View` that drops `onRefresh` and `refreshing`: no pull-to-refresh | | `KeyboardAvoidingView` | a mock; browsers expose no keyboard frame to avoid | | `StatusBar` | renders `null`; setters do nothing | | `YellowBox` | an unimplemented view | The red outline in development is deliberate: it marks places where a native component has no web implementation. In production it disappears, and the stub is an unstyled box. ## Partial implementations - **`ScrollView`**: no momentum scroll events; code that waits for momentum end to snap or load more never hears it. - **`Text`**: no `onLongPress`. - **`TextInput`**: no rich-text features and no auto-expanding height. - **`Image`**: no multiple sources and no HTTP headers on the request. - **`Animated`**: no native driver. With `useNativeDriver: true`, the vendored animation code warns that the native animated module is missing and falls back to JavaScript-driven animation. - **`AccessibilityInfo`**: mostly mocked; `isScreenReaderEnabled()` always resolves `true`. ## Layout differences to expect 1. **The page must give the app room.** React Native Web's docs note that the HTML root elements may need styles (full height, flex) for the app to fill the viewport; a root `View` with `flex: 1` inside an unsized page collapses. 2. **Shorthand styles take one value** and `flex` accepts only positive integers, `0` or `-1`. 3. **Text metrics** come from the browser's fonts, so line breaks and truncation differ from native. ## The reverse gap: web-only survivors React Native Web keeps some APIs that React Native core has removed, because the web port moves at its own pace: - **`InteractionManager`**: exported and working in React Native Web, but removed from React Native 0.87 core, where accessing it in development throws an invariant that recommends `requestIdleCallback`. - **`CheckBox`** and **`Picker`**: exported by React Native Web; React Native core does not ship them (community packages replaced them). - **`Touchable*` components**: still work in both, but `Pressable` is the current API. So code tested only in the browser can use an API that fails on a phone. React Native Web's compatibility page states the policy: features deprecated in React Native should be considered unsupported in React Native for Web, even where they still happen to work. ## Why the web port lags and leads React Native Web is a separate library with its own release cadence, and it reimplements React Native's JavaScript surface rather than sharing code with core for most of it (it vendors some pieces, such as `Animated`). Two consequences follow: - It can **lag**: new core features and new props arrive on the web later, or not at all, so a prop added in a recent React Native release may simply be ignored in the browser. - It can **lead in the wrong direction**: APIs core removed stay available on the web until the library drops them, which makes the browser a poor oracle for what is still supported. The fix is to treat current React Native as the source of truth and the web port as an implementation of it, checking the compatibility table rather than assuming. ## Practical guard rails - Treat the web as a third platform in QA, not as a preview of the app. - Grep for stubbed components (`TouchableNativeFeedback`, `RefreshControl`) and replace them with portable ones (`Pressable`, a web refresh button). - Lint against APIs removed from current React Native, so the web build cannot keep them alive. - Test gestures and scroll-driven logic in a browser explicitly, since momentum and long-press events do not arrive.
- In React Native Web, why might a FlatList's snap-after-scroll logic never run?Because React Native Web's `ScrollView`, which `FlatList` builds on, does not emit momentum scroll events. Logic hung on momentum end never fires in a browser; use `onScroll` with a debounce, or CSS scroll snapping in a web-specific file.
- What happens in React Native 0.87 when code written against React Native Web calls InteractionManager.runAfterInteractions?In a development build React Native 0.87 throws an invariant on accessing `InteractionManager`, saying it was removed from core and recommending `requestIdleCallback`. React Native Web still exports it, so the call works in the browser and only fails on the phone. Replace it with `requestIdleCallback` or split the work into smaller tasks.
saying these in an interview costs you the question
- If a component renders on the web, it behaves the same as on native.
- RefreshControl gives pull-to-refresh in the browser.
- useNativeDriver: true runs animations off the main thread in the browser.
- Anything React Native Web exports is safe to use on React Native 0.87.
- The red outline around TouchableNativeFeedback also appears in production.