Shipnative
ShipnativeShipnative
Sign in
GuideAugust 2026 · 9 min read

React Native WebView: Setup, Messaging, and Limits

A WebView is React Native’s escape hatch: a full browser engine you can drop into a screen when the thing you need already exists on the web and rebuilding it natively would be absurd. It is genuinely useful, and it is also the component most likely to produce a blank screen, a lost session, or an App Store rejection. This guide covers the install, the props that actually matter, the two-way message bridge, and the specific situations where reaching for a WebView is the wrong call.

Install and the minimum that works

npx expo install react-native-webview

Use expo install rather than npm install — the library contains native code, and the version has to line up with your Expo SDK. It runs in Expo Go, so you do not need a development build just to try it. In a bare React Native project, install with npm and run npx pod-install.

import { useRef, useState } from 'react';
import { ActivityIndicator, StyleSheet, View } from 'react-native';
import { WebView } from 'react-native-webview';

export default function HelpCentre() {
  const [loading, setLoading] = useState(true);

  return (
    <View style={styles.fill}>
      <WebView
        source={{ uri: 'https://example.com/help' }}
        style={styles.fill}
        onLoadEnd={() => setLoading(false)}
        onError={(e) => console.warn('webview error', e.nativeEvent)}
        onHttpError={(e) => console.warn('http', e.nativeEvent.statusCode)}
        // stop taps on external links from hijacking the screen
        setSupportMultipleWindows={false}
        allowsBackForwardNavigationGestures
      />
      {loading && <ActivityIndicator style={StyleSheet.absoluteFill} />}
    </View>
  );
}

const styles = StyleSheet.create({ fill: { flex: 1 } });

The flex: 1 is not decoration. A WebView does not measure itself against its content the way a View does — with no height constraint it collapses to nothing and you get the blank screen that sends people to Stack Overflow. If the WebView sits inside a ScrollView, give it an explicit numeric height, because “as tall as the page” is information only the page has.

When a WebView is the right answer

Use caseVerdictWhy
Third-party checkout or payment page✅ Good fitYou must not reimplement it, and PCI scope stays out of your app
Help centre, docs, changelog, legal text✅ Good fitContent changes without a store release
OAuth / SSO consent screen⚠️ Use the auth session APIProviders increasingly block embedded WebViews
A rich text or code editor you already have on web⚠️ WorkableGreat reuse, but keyboard and selection need real work
Your entire app, wrapped❌ Bad fitGuideline 4.2 rejection, and it feels wrong on device
Rows inside a scrolling list❌ Bad fitA browser engine per row — memory and jank

The pattern in that table is a single question: is the web content a component of your app, or is it the app? A checkout inside an otherwise native shopping flow is a component. A login screen, a product list, and a settings page all served from your website is the app, and reviewers treat it as one.

The OAuth row deserves its own note. Google, and a growing number of identity providers, refuse to serve their consent screens inside an embedded WebView, because a host app can read everything typed into it. Use expo-auth-session or an in-app browser tab instead — the authentication guide has the working setup.

Talking to the page, and hearing back

The bridge is two one-way string channels. From the page, you post up; from React Native, you inject down.

const ref = useRef(null);

// runs once when the document loads — must end with true;
const INJECTED = `
  document.addEventListener('click', (e) => {
    const el = e.target.closest('[data-native]');
    if (el) window.ReactNativeWebView.postMessage(
      JSON.stringify({ type: 'action', id: el.dataset.native })
    );
  });
  true;
`;

function onMessage(event) {
  let msg;
  try { msg = JSON.parse(event.nativeEvent.data); } catch { return; }
  if (msg?.type === 'action') handleAction(msg.id);   // never trust the shape
}

// native → web, any time after load
function applyTheme(theme) {
  ref.current?.injectJavaScript(
    `document.documentElement.dataset.theme = ${JSON.stringify(theme)}; true;`
  );
}

<WebView
  ref={ref}
  source={{ uri: PAGE }}
  injectedJavaScript={INJECTED}
  onMessage={onMessage}
/>;

Two details that cost people an afternoon each. Injected scripts must evaluate to a value — end the string with true; or iOS may warn and behave inconsistently. And an onMessage handler must exist for the bridge to be installed at all on some platforms, so if postMessage appears to do nothing, check you passed the prop.

Treat every message as untrusted input. Any script running on that page — including one you did not write, on a domain you do not control — can call window.ReactNativeWebView.postMessage. Validate the type, validate the fields, and never eval, navigate, or write to storage from a message body without checking it first. If the page loads third-party content, restrict what the bridge can trigger to a small allowlist of actions.

The five problems everyone hits

  1. Sessions do not carry over. The WebView keeps its own cookie jar. Set sharedCookiesEnabled on iOS, thirdPartyCookiesEnabled where you need it on Android, and note that source={{ uri, headers }} applies those headers to the first request only — every link tapped afterwards goes out bare.
  2. Android back closes the screen instead of going back. Wire onNavigationStateChange to track canGoBack, then handle the hardware back button and call ref.current.goBack() while there is history left.
  3. File inputs do nothing on Android. Uploading needs allowFileAccess plus the camera and storage permissions declared in your app config. On iOS the picker works out of the box; on Android an <input type="file"> silently fails without them.
  4. Local HTML paths break in release builds. source={{ html }} is reliable for a self-contained string; bundled asset files resolve differently once packaged. If you are shipping a local editor or renderer, inline the CSS and JavaScript into the HTML string and test a release build early.
  5. The keyboard covers the field.The page’s own scrolling does not know about your native layout. Give the WebView a real flex container rather than nesting it in a scroll view, and see the KeyboardAvoidingView guide for the platform behaviour underneath it.

Build the screen natively, keep the WebView for what it is good at

The reason wrapped-website apps get rejected is also the reason users uninstall them: scrolling feels wrong, the back gesture does the wrong thing, and nothing works offline. A WebView earns its place when it is one screen out of fifteen, doing a job you genuinely should not rebuild.

If the reason you are reaching for a WebView is that building the native screens sounds slow, that is worth testing before you commit. Describe the app — “a store with a product list, product detail, cart, and a hosted checkout” — and ShipNative generates it as real React Native running on your phone, with the WebView left where it belongs: around the checkout. You own and export the code either way.

Frequently Asked Questions

How do I add a WebView in React Native?

Install react-native-webview — in Expo, run npx expo install react-native-webview so the native version matches your SDK. Then render <WebView source={{ uri: "https://example.com" }} style={{ flex: 1 }} />. The library ships native code, so bare React Native projects also need a pod install on iOS. It works in Expo Go because it is bundled with the Expo SDK.

Why is my React Native WebView blank or white?

Three usual causes. The WebView has no height — it needs flex: 1 or an explicit height, because it does not size to its content. The URL is plain http and both platforms block cleartext traffic by default. Or the page failed and you never rendered the error, so add onError and onHttpError handlers and log nativeEvent before assuming the WebView is broken.

How do I send data between React Native and a WebView?

Two directions, two APIs. Web to native: call window.ReactNativeWebView.postMessage(JSON.stringify(payload)) inside the page and read it in the onMessage prop. Native to web: call the ref method injectJavaScript with a string of code, or set injectedJavaScript to run once on load. Both channels move strings only, so serialise with JSON.stringify and parse defensively — a message can arrive from any script on the page.

Will Apple reject an app that is just a WebView?

Frequently, yes. App Store Review Guideline 4.2 asks for apps that do more than repackage a website, and a shell around an existing site is the classic rejection. A WebView used for one part of an otherwise native app — a checkout, a help centre, a legal document, a third-party dashboard — is normal and passes review routinely.

Does a WebView share cookies or login state with the app?

Not automatically. The WebView has its own cookie store, separate from anything fetch does in JavaScript. Set sharedCookiesEnabled on iOS to use the shared NSHTTPCookieStorage, and use thirdPartyCookiesEnabled on Android where relevant. If your session lives in a header or token rather than a cookie, pass it through the source prop headers, and remember that headers apply to the first request only — not to links the user taps afterwards.

Is a WebView slower than native screens?

It costs more memory and starts slower, because you are booting a browser engine inside your app. A single WebView on a screen the user visits occasionally is fine. WebViews inside list rows, or several mounted at once, is where apps start dropping frames and getting killed in the background on Android.

→

React Native Authentication in 2026

Why OAuth belongs in an auth session, not an embedded WebView.

Read guide →
→

React Native Performance: 10 Fixes

WebViews are a memory cost — this is the wider budget.

Read guide →

Ship a real React Native app today

Describe, preview, and export Expo code — free to start.

Build with ShipNative →
ShipNative logoShipnative

Build mobile apps with AI. Describe, preview, and ship to iOS & Android in minutes.

Features

Text to App AIApp Generator from ScreenshotPRD to Mobile App

Tools

All free toolsApp Cost CalculatorApp Name GeneratorApp Store Keyword ToolReact Native Components

Blog

All blog postsHow to Build an App Without CodingBest AI Tools for Real Mobile AppsExpo EAS App Store ChecklistLovable, Cursor & v0 for MobileBest AI App Builders in 2026React Native AI App Builder Guide

Legal

FAQTerms of ServicePrivacy Policy

© 2026 ShipNative. All rights reserved.