skip to content

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.