skip to content

Browser-Only Gaps

Native modules, platform-only components and deprecated APIs have no web implementation, so shared code branches for the web. Interviewers ask when a separate web UI beats branching.

on this pageshow

explore

questions

5

In React Native Web, what happens when shared code calls an API with no web implementation, such as Alert.alert, findNodeHandle or a native module?

level: middleimportance: must knowfreq 38%

answer

  1. three failure shapes, not one
  2. some throw, some quietly do nothing
  3. Alert.alert: an empty method
  4. findNodeHandle: use the ref instead
  5. NativeModules holds only UIManager

basics

~20 s

It depends on the API. React Native Web throws for some (findNodeHandle), silently mocks others (Alert.alert does nothing, StatusBar renders null, NativeModules holds only UIManager) and lacks platform-only exports such as PermissionsAndroid, which arrive as undefined.

solid answer

~40 s

React Native Web implements most of React Native's JavaScript API, but gaps fail in three different ways. Some **throw**: `findNodeHandle` throws "findNodeHandle is not supported on web. Use the ref property on the component instead.", and in an Expo project `requireNativeModule` throws `Cannot find native module '…'` in the browser. Some are **mocks that do nothing**: `Alert.alert` is an empty method, `StatusBar` renders `null`, `BackHandler.addEventListener` logs an error and returns a no-op subscription, `KeyboardAvoidingView` is a mock, and `NativeModules` contains only `UIManager`. And some are **not exported at all** (`PermissionsAndroid`, `ToastAndroid`, `ActionSheetIOS`, `TurboModuleRegistry`), so the import is `undefined` and the first call is a `TypeError`. The silent ones are the dangerous ones: a confirmation dialog that never appears looks like a working button.

code

typescript · 5 lines
typescript
// confirm.web.ts: web version of a shared confirm() helper
export function confirm(title: string, message: string): Promise<boolean> {
  // Alert.alert is an empty method in react-native-web, so use the browser dialog
  return Promise.resolve(window.confirm(`${title}\n\n${message}`));
}

go deeper

for a junior

Recall that some React Native APIs do nothing or throw on the web, with Alert.alert and findNodeHandle as the standard examples.

for a middle

Explain the three failure shapes (throw, silent mock, missing export) with examples of each, and the ref-based replacement for findNodeHandle.

for a senior

Show how you would audit a codebase before a web launch, why silent mocks are the riskiest, and how adapters and platform files contain each gap.

for a principal

Frame the web gap inventory as a scoping input: the count of native-only features decides whether shared UI is realistic or the web needs its own product surface.

## Why the gaps exist **React Native Web** (0.21 here) reimplements React Native's JavaScript API with browser primitives. Anything that exists only because a phone has it (an Android back button, a status bar, a native module compiled into the app binary) has either no browser equivalent or a weak one. The library's compatibility page says it plainly: most of the API is covered, some parts are mocked, a few are not started, and features deprecated in React Native should be considered unsupported on the web. The practical skill is knowing **how** each gap fails, because the failure mode decides how you find it. ## Shape 1: it throws - **`findNodeHandle`** throws: "findNodeHandle is not supported on web. Use the ref property on the component instead." On the web a host ref *is* the DOM element, and React Native Web adds `measure`, `measureInWindow` and `measureLayout` to it, so the ref replaces the node handle. - **Expo-style modules without a web implementation**: `requireNativeModule('X')` from `expo-modules-core` throws `Cannot find native module 'X'` in a browser. Expo's own packages avoid it by shipping a `.web.ts` file that never calls it; a package without one gets the throw. During server rendering, where `window` is undefined, `requireNativeModule` returns an empty object instead so static rendering does not crash. - **Expo API methods missing on the platform** throw an `UnavailabilityError` with code `ERR_UNAVAILABLE` and a message like "The method or property expo-local-authentication.authenticateAsync is not available on web". These are the easy ones: the error names the problem. ## Shape 2: it silently does nothing | API | Behaviour in React Native Web | |---|---| | `Alert.alert` | an empty static method: no dialog, no callback | | `StatusBar` | renders `null`; its static setters are no-ops | | `BackHandler.addEventListener` | logs "BackHandler is not supported on web and should not be used." and returns a no-op subscription | | `KeyboardAvoidingView` | a mock; the web has no keyboard frame to avoid | | `RefreshControl` | renders a plain `View`; there is no pull-to-refresh | | `NativeModules` | an object holding only `UIManager` | | `AccessibilityInfo.isScreenReaderEnabled()` | always resolves `true`, since browsers do not expose it | | `LogBox` | `ignoreLogs` and friends are empty functions | | `Vibration.vibrate` | calls the browser's vibrate API only if the browser has one | These are the dangerous ones. A delete button that calls `Alert.alert` for confirmation does nothing at all on the web: no dialog, no error, and the `onPress` of the destructive option never runs. ## Shape 3: it is not exported React Native Web's entry point simply does not export Android-only or iOS-only APIs such as `PermissionsAndroid`, `ToastAndroid`, `ActionSheetIOS` and `Settings`, nor the native-module plumbing `TurboModuleRegistry` and `requireNativeComponent`. `import { ToastAndroid } from 'react-native'` therefore gives `undefined` in the web bundle (a bundler may warn about the missing export), and the first call fails with a `TypeError`. A third-party library that calls `TurboModuleRegistry.getEnforcing` at module scope fails the moment it is imported, before any of your code runs. ## How to handle each shape 1. **Inventory before you port.** Search the codebase for `Alert`, `BackHandler`, `StatusBar`, `NativeModules`, `findNodeHandle` and every native-only dependency; check each library for a web implementation. 2. **Replace mocks with real web behaviour.** An `Alert.web.ts` adapter that uses the browser's confirm dialog, or an in-app modal, keeps one call site across platforms. 3. **Replace `findNodeHandle` with refs** and the `measure` methods React Native Web adds to them. 4. **Keep native-only imports out of the web bundle** with a platform file (`Scanner.web.tsx` next to `Scanner.tsx`) rather than an inline branch, because the import itself can crash. 5. **Catch `UnavailabilityError`** (or check an `isAvailableAsync`-style function where the Expo module provides one) and degrade the feature on the web. ## A quick way to explain it in an interview "React Native Web fails in three ways: it throws, it no-ops, or the export is missing. I look for the silent no-ops first, because they are the ones that pass every test and still break the product."

  • In React Native Web, what replaces findNodeHandle when you need a component's position?
    The ref itself. On the web a host component's ref is the DOM element, and React Native Web attaches `measure`, `measureInWindow` and `measureLayout` to it, so `ref.current.measureInWindow(cb)` works without a node handle. `findNodeHandle` throws on the web by design.
  • In an Expo project, what does requireNativeModule do on the web during static rendering compared with in the browser?
    In the browser it throws `Cannot find native module 'X'`; Expo's own packages avoid that by shipping a `.web.ts` implementation that never calls it. During server or static rendering, where `window` is undefined, it returns an empty object so the page can still render. So a package with no web file can pass static rendering and only fail once the page loads.
  • Why is AccessibilityInfo.isScreenReaderEnabled() unreliable in React Native Web?
    Browsers do not tell pages whether a screen reader is running, so React Native Web resolves it to `true` unconditionally. Code that hides content or changes behaviour based on it will always take the screen-reader branch on the web; build the accessible version by default instead of switching on this value.

A silent mock is like a light switch wired to nothing in a rented flat: it clicks, it looks right, and you only notice when the room stays dark. A switch that sparks (an API that throws) is alarming but gets fixed on day one.

saying these in an interview costs you the question

  • React Native Web throws an error for every unsupported API.
  • Alert.alert falls back to window.alert automatically.
  • NativeModules on the web can load a module compiled into the app.
  • PermissionsAndroid is exported as a no-op on the web.
  • findNodeHandle works on the web and returns the DOM node.
open as a page

With React Native Web, what does Platform report in a browser, and when is a .web.tsx file better than an inline Platform.OS check?

level: middleimportance: should knowfreq 36%

basics

~10 s

In React Native Web, Platform.OS is 'web' and Platform.select picks the web key, then default. Prefer a .web.tsx file when the native version imports a native-only package: an inline branch still evaluates that import.

open as a page

An Expo app's scan screen imports a native-only camera module with no web build, and the web bundle crashes on load; how do you ship a web version?

level: seniorimportance: should knowfreq 22%

basics

~20 s

Move the native-only import behind a platform file so the web bundle never loads it, then give ScanScreen.web.tsx a browser path: a camera library with a web implementation, a file input with capture, or a clear 'use the app' fallback.

open as a page

For a React Native product adding a web version, how do you decide between sharing the UI through React Native Web and building a separate web app?

level: principalimportance: should knowfreq 30%

basics

~10 s

Share UI through React Native Web when the web product is app-like and most screens use portable components; build web UI separately when it is content-led, SEO-led or native-feature-heavy, and share logic either way.

open as a page

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?

level: middleimportance: nice to knowfreq 18%

basics

~20 s

Some 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.

open as a page