Skip to content

Example React Navigation

This guide is about integrating WebView, the web, and React Navigation.

Example: react-navigation

React Native Part

tsx
// This file is src/navigation.ts

import type { NativeStackScreenProps } from '@react-navigation/native-stack';

export type RootStackParamList = {
  Home: undefined;
  Profile: { userId: string };
  Feed: { sort: 'latest' | 'top' } | undefined;
};
tsx
// This file is src/bridge.ts

import {
  StackActions,
  createNavigationContainerRef,
} from "@react-navigation/native";
import { createWebView, bridge } from "@webview-bridge/react-native";
import InAppBrowser from "react-native-inappbrowser-reborn";
import { RootStackParamList } from "./navigation";

export const navigationRef = createNavigationContainerRef<RootStackParamList>();

const existsScreen = (name: string): boolean => {
  return Boolean(
    navigationRef.current?.isReady() &&
      navigationRef.current
        .getState()
        .routeNames.find((routeName) => routeName === name),
  );
};

export const appBridge = bridge({
  async getMessage() {
    return "I'm from native" as const;
  },
  async openInAppBrowser(url: string) {
    if (await InAppBrowser.isAvailable()) {
      await InAppBrowser.open(url);
    }
  },
  async canGoBack() {
    return Boolean(
      navigationRef.current?.isReady() && navigationRef.current.canGoBack(),
    );
  },
  async goBack() {
    if (navigationRef.current?.isReady()) {
      navigationRef.current.goBack();
    }
  },
  async navigate<RouteName extends keyof RootStackParamList>(
    name: RouteName,
    params: RootStackParamList[RouteName],
  ) {
    if (navigationRef.current?.isReady()) {
      if (!existsScreen(name)) {
        throw new Error(`Screen ${name} not found`);
      }
      navigationRef.current.navigate(name as any, params as any);
    }
  },
  async push<RouteName extends keyof RootStackParamList>(
    name: RouteName,
    params: RootStackParamList[RouteName],
  ) {
    if (navigationRef.current?.isReady()) {
      if (!existsScreen(name)) {
        throw new Error(`Screen ${name} not found`);
      }
      navigationRef.current.dispatch(StackActions.push(name, params));
    }
  },
  async replace<RouteName extends keyof RootStackParamList>(
    name: RouteName,
    params: RootStackParamList[RouteName],
  ) {
    if (navigationRef.current?.isReady()) {
      if (!existsScreen(name)) {
        throw new Error(`Screen ${name} not found`);
      }
      navigationRef.current.dispatch(StackActions.replace(name, params));
    }
  },
  async popToTop() {
    if (navigationRef.current?.isReady()) {
      navigationRef.current.dispatch(StackActions.popToTop());
    }
  },
});

// It is exported via the package.json type field.
export type AppBridge = typeof appBridge;

export const { WebView, linkWebMethod } = createWebView({
  bridge: appBridge,
  debug: true,
  fallback: (method) => {
    console.warn(`Method '${method}' not found in native`);
  },
});
tsx
// This file is App.tsx
import React from "react";

import { NavigationContainer } from "@react-navigation/native";
import { createNativeStackNavigator } from "@react-navigation/native-stack";
import type { NativeStackScreenProps } from "@react-navigation/native-stack";
import { View, Text, Button } from "react-native";
import { WebView, navigationRef } from "./src/bridge";
import { RootStackParamList } from "./src/navigation";

function WebViewHomeScreen() {
  return (
    <View style={{ height: "100%" }}>
      <WebView
        source={{
          uri: "http://localhost:5173",
        }}
        style={{ height: "100%", flex: 1, width: "100%" }}
      />
    </View>
  );
}

function UserInfoScreen({
  navigation,
  route,
}: NativeStackScreenProps<RootStackParamList, "UserInfo">) {
  const { userId } = route.params;

  return (
    <View style={{ height: "100%" }}>
      <Text>UserId: {userId}</Text>

      <Button
        title="New WebView"
        onPress={() => navigation.push("WebViewHome")}
      />
      <Button title="Go back" onPress={() => navigation.goBack()} />
    </View>
  );
}

const Stack = createNativeStackNavigator<RootStackParamList>();

function App(): JSX.Element {
  return (
    <NavigationContainer ref={navigationRef}>
      <Stack.Navigator initialRouteName="WebViewHome">
        <Stack.Screen name="WebViewHome" component={WebViewHomeScreen} />
        <Stack.Screen name="UserInfo" component={UserInfoScreen} />
      </Stack.Navigator>
    </NavigationContainer>
  );
}

export default App;

Web (React) Part

tsx
// This file is App.tsx
import { useState } from "react";
import { linkBridge } from "@webview-bridge/web";
import type { AppBridge } from "@webview-bridge-example-react-navigation/react-native";

const bridge = linkBridge<AppBridge>({
  throwOnError: true,
  onReady: () => {
    console.log("bridge is ready");
  },
});

function App() {
  const [userId, setUserId] = useState("");

  return (
    <div>
      <h3>This is a web page.</h3>

      <div
        style={{
          display: "flex",
          flexDirection: "column",
          gap: "12px",
        }}
      >
        <button
          onClick={() => {
            if (bridge.isNativeMethodAvailable("openInAppBrowser") === true) {
              bridge.openInAppBrowser(
                "https://github.com/gronxb/webview-bridge",
              );
            }
          }}
        >
          open InAppBrowser
        </button>

        <input
          type="text"
          style={{
            fontSize: "16px",
          }}
          value={userId}
          onChange={(e) => setUserId(e.target.value)}
          placeholder="please userId"
        />
        <button
          onClick={() => {
            bridge.push("UserInfo", { userId });
          }}
        >
          Go UserInfo
        </button>

        <button
          onClick={async () => {
            if (await bridge.canGoBack()) {
              bridge.goBack();
            } else {
              alert("Can't go back");
            }
          }}
        >
          Go Back
        </button>
      </div>
    </div>
  );
}

export default App;