Skip to content

Instantly share code, notes, and snippets.

@lam0819
Created May 19, 2025 04:39
Show Gist options
  • Save lam0819/e7e961de28df4396f1840cd2c9df9eb5 to your computer and use it in GitHub Desktop.
Save lam0819/e7e961de28df4396f1840cd2c9df9eb5 to your computer and use it in GitHub Desktop.
React Native Interact with Webview

CustomWebView Component

Table of Contents

  1. English
  2. 简体中文 (Simplified Chinese)
  3. 繁體中文 (Traditional Chinese)

English

A reusable React Native WebView component designed for flexible interaction between web content and your React Native application. It simplifies common tasks like navigating React Native screens from the web, triggering native share functionality, and injecting JavaScript for two-way communication.

Core Idea

The CustomWebView component provides a standardized JavaScript bridge (window.ReactNativeWebViewBridge) that is automatically injected into your web content. This bridge allows your web application's JavaScript to easily send structured messages to the React Native application, and vice-versa.

Key Features

  • Standardized JS Bridge: Injects window.ReactNativeWebViewBridge into the web content for easy communication.
  • Web to React Native Communication:
    • window.ReactNativeWebViewBridge.navigate(details): Request React Native to navigate to a different screen.
    • window.ReactNativeWebViewBridge.share(details): Request React Native to trigger the native sharing dialog.
    • window.ReactNativeWebViewBridge.sendEvent(eventType, eventData): Send custom events with arbitrary data.
    • window.ReactNativeWebViewBridge.postMessage({ type: "...", payload: ... }): The underlying method for all bridge communication.
  • React Native to Web Communication:
    • Use the component's ref to call injectJavaScript("yourJSCode(); true;") to execute JavaScript within the WebView.
  • Customizable:
    • Pass uri to load any web page.
    • Handle messages from the web via the onMessage prop.
    • Inject custom JavaScript before or after the main bridge setup.
    • Set custom HTTP headers (e.g., for authentication).
    • Control loading indicators.
  • Exposes WebView Controls: Provides access to common WebView methods like reload(), goBack(), etc., via its ref.

How to Use (React Native Side)

  1. Import the component:

    import CustomWebView, { WebViewRef } from './path/to/your/CustomWebView';
    import { useRef } from 'react';
    import { useNavigation } from '@react-navigation/native'; // Or your navigation library
    import { Share, Alert } from 'react-native';
  2. Use it in your component:

    const MyScreenWithWebView = () => {
      const webViewRef = useRef<WebViewRef>(null);
      const navigation = useNavigation();
    
      const handleWebViewMessage = (message) => {
        console.log('RN: Received message:', message.type, message.payload);
        switch (message.type) {
          case 'NAVIGATE':
            if (message.payload?.routeName) {
              navigation.navigate(message.payload.routeName, message.payload.params);
            }
            break;
          case 'SHARE':
            if (message.payload) {
              Share.share({
                message: message.payload.message || '',
                url: message.payload.url,
                title: message.payload.title,
              });
            }
            break;
          case 'CUSTOM_EVENT_FROM_WEB':
            Alert.alert('Custom Event', `Received: ${JSON.stringify(message.payload)}`);
            break;
          // ... handle other message types
        }
      };
    
      const sendDataToWebView = () => {
        if (webViewRef.current) {
          const script = `
            if (window.receiveDataFromRN) {
              window.receiveDataFromRN({ content: 'Hello from React Native!' });
            }
            true; // Important!
          `;
          webViewRef.current.injectJavaScript(script);
        }
      };
    
      return (
        <CustomWebView
          ref={webViewRef}
          uri="https://your-web-app.com/page.html"
          onMessage={handleWebViewMessage}
          // Optional props
          authToken="your_auth_token_if_needed"
          showsLoadingIndicator={true}
          onShouldStartLoadWithRequest={(request) => {
            // Control navigation: return true to allow, false to block
            // or open in system browser: Linking.openURL(request.url); return false;
            console.log('Attempting to load:', request.url);
            return true;
          }}
        />
        // <Button title="Send to Web" onPress={sendDataToWebView} />
      );
    };

How to Use (Web Page Side - for Web Developers)

Your web application's JavaScript can interact with the React Native app using the window.ReactNativeWebViewBridge object.

<!DOCTYPE html>
<html>
<head>
  <title>Web Page</title>
  <script>
    function requestNavigation() {
      if (window.ReactNativeWebViewBridge && window.ReactNativeWebViewBridge.navigate) {
        window.ReactNativeWebViewBridge.navigate({ routeName: 'UserProfile', params: { id: '123' } });
      } else {
        console.warn('ReactNativeWebViewBridge.navigate not available.');
      }
    }

    function requestShare() {
      if (window.ReactNativeWebViewBridge && window.ReactNativeWebViewBridge.share) {
        window.ReactNativeWebViewBridge.share({
          url: window.location.href,
          message: 'Check out this page!',
          title: document.title
        });
      } else {
        console.warn('ReactNativeWebViewBridge.share not available.');
      }
    }

    function sendCustomEvent() {
      if (window.ReactNativeWebViewBridge && window.ReactNativeWebViewBridge.sendEvent) {
        window.ReactNativeWebViewBridge.sendEvent('MY_CUSTOM_ACTION', { data: 'some value', timestamp: Date.now() });
      } else {
        console.warn('ReactNativeWebViewBridge.sendEvent not available.');
      }
    }

    // Function to be called by React Native's injectJavaScript
    window.receiveDataFromRN = function(data) {
      console.log('Web: Received data from RN:', data);
      alert('Message from RN: ' + data.content);
    };
  </script>
</head>
<body>
  <button onclick="requestNavigation()">Go to Profile (RN)</button>
  <button onclick="requestShare()">Share this Page (Native)</button>
  <button onclick="sendCustomEvent()">Send Custom Event to RN</button>
</body>
</html>

Main Props

Prop Type Default Description
uri string Required The URL of the web page to load.
onMessage (message: { type: string; payload?: any }) => void Required Callback function to handle messages sent from the WebView's JavaScript via the bridge.
authToken string undefined Optional authentication token to be included in the Authorization header.
acceptLanguage string undefined Optional Accept-Language header value.
additionalHeaders Record<string, string> {} Any other custom HTTP headers to send with the initial request.
injectedJavaScriptBeforeBridge string undefined Custom JavaScript string to inject before the window.ReactNativeWebViewBridge is set up.
injectedJavaScriptAfterBridge string undefined Custom JavaScript string to inject after the window.ReactNativeWebViewBridge is set up.
baseInjectedJavaScript string Internal bridge script Allows overriding the entire default bridge script if needed for advanced customization.
showsLoadingIndicator boolean true Whether to show a default loading indicator while the page loads.
customLoadingComponent React.ReactNode undefined A custom React component to display as a loading indicator.
style ViewStyle { flex: 1 } Style for the WebView container.
onShouldStartLoadWithRequest (event: WebViewNavigationEvent) => boolean undefined (passes to react-native-webview) Function that allows you to intercept web requests and decide whether to load them in the WebView or handle them differently (e.g., open externally).
... WebViewProps Other standard react-native-webview props are passed through.

Exposed Ref Methods (WebViewRef)

You can get a ref to the CustomWebView component to access these methods:

  • injectJavaScript(script: string): Executes JavaScript code within the loaded web page.
  • reload(): Reloads the current web page.
  • goBack(): Navigates back in the WebView's history.
  • goForward(): Navigates forward in the WebView's history.
  • stopLoading(): Stops the WebView from loading the current page.

简体中文 (Simplified Chinese)

一个可复用的 React Native WebView 组件,专为实现 Web 内容与 React Native 应用之间的灵活交互而设计。它简化了常见任务,例如从 Web 页面导航到 React Native 屏幕、触发原生分享功能以及注入 JavaScript 以进行双向通信。

###核心理念

CustomWebView 组件提供了一个标准化的 JavaScript 桥接 (window.ReactNativeWebViewBridge),它会自动注入到您的 Web 内容中。这个桥接允许您的 Web 应用的 JavaScript 轻松地向 React Native 应用发送结构化消息,反之亦然。

###主要特性

  • 标准化 JS 桥接: 向 Web 内容注入 window.ReactNativeWebViewBridge 以方便通信。
  • Web 到 React Native 通信:
    • window.ReactNativeWebViewBridge.navigate(details): 请求 React Native 导航到不同的屏幕。
    • window.ReactNativeWebViewBridge.share(details): 请求 React Native 触发原生分享对话框。
    • window.ReactNativeWebViewBridge.sendEvent(eventType, eventData): 发送带有任意数据的自定义事件。
    • window.ReactNativeWebViewBridge.postMessage({ type: "...", payload: ... }): 所有桥接通信的底层方法。
  • React Native 到 Web 通信:
    • 使用组件的 ref 调用 injectJavaScript("yourJSCode(); true;") 在 WebView 内执行 JavaScript。
  • 可定制:
    • 传递 uri 加载任何网页。
    • 通过 onMessage prop 处理来自 Web 的消息。
    • 在主桥接设置之前或之后注入自定义 JavaScript。
    • 设置自定义 HTTP 头部 (例如,用于身份验证)。
    • 控制加载指示器。
  • 暴露 WebView 控件: 通过其 ref 提供对常见 WebView 方法的访问,如 reload()goBack() 等。

###如何使用 (React Native 端)

  1. 导入组件:

    import CustomWebView, { WebViewRef } from './path/to/your/CustomWebView'; // 调整路径
    import { useRef } from 'react';
    import { useNavigation } from '@react-navigation/native'; // 或您的导航库
    import { Share, Alert } from 'react-native';
  2. 在您的组件中使用它:

    const MyScreenWithWebView = () => {
      const webViewRef = useRef<WebViewRef>(null);
      const navigation = useNavigation();
    
      const handleWebViewMessage = (message) => {
        console.log('RN: 收到消息:', message.type, message.payload);
        switch (message.type) {
          case 'NAVIGATE':
            if (message.payload?.routeName) {
              navigation.navigate(message.payload.routeName, message.payload.params);
            }
            break;
          case 'SHARE':
            if (message.payload) {
              Share.share({
                message: message.payload.message || '',
                url: message.payload.url,
                title: message.payload.title,
              });
            }
            break;
          case 'CUSTOM_EVENT_FROM_WEB':
            Alert.alert('自定义事件', `收到: ${JSON.stringify(message.payload)}`);
            break;
          // ... 处理其他消息类型
        }
      };
    
      const sendDataToWebView = () => {
        if (webViewRef.current) {
          const script = `
            if (window.receiveDataFromRN) {
              window.receiveDataFromRN({ content: '来自 React Native 的问候!' });
            }
            true; // 重要!
          `;
          webViewRef.current.injectJavaScript(script);
        }
      };
    
      return (
        <CustomWebView
          ref={webViewRef}
          uri="https://your-web-app.com/page.html" // 替换为您的 Web 应用 URL
          onMessage={handleWebViewMessage}
          // 可选 props
          authToken="your_auth_token_if_needed" // 如果需要,传入认证 token
          showsLoadingIndicator={true}
          onShouldStartLoadWithRequest={(request) => {
            // 控制导航: 返回 true 允许加载, false 阻止
            // 或在系统浏览器中打开: Linking.openURL(request.url); return false;
            console.log('尝试加载:', request.url);
            return true;
          }}
        />
        // <Button title="发送到 Web" onPress={sendDataToWebView} />
      );
    };

###如何使用 (Web 页面端 - 供 Web 开发人员使用)

您的 Web 应用的 JavaScript 可以使用 window.ReactNativeWebViewBridge 对象与 React Native 应用进行交互。

<!DOCTYPE html>
<html>
<head>
  <title>Web 页面</title>
  <script>
    function requestNavigation() {
      if (window.ReactNativeWebViewBridge && window.ReactNativeWebViewBridge.navigate) {
        window.ReactNativeWebViewBridge.navigate({ routeName: 'UserProfile', params: { id: '123' } });
      } else {
        console.warn('ReactNativeWebViewBridge.navigate 不可用。');
      }
    }

    function requestShare() {
      if (window.ReactNativeWebViewBridge && window.ReactNativeWebViewBridge.share) {
        window.ReactNativeWebViewBridge.share({
          url: window.location.href,
          message: '快来看看这个页面!',
          title: document.title
        });
      } else {
        console.warn('ReactNativeWebViewBridge.share 不可用。');
      }
    }

    function sendCustomEvent() {
      if (window.ReactNativeWebViewBridge && window.ReactNativeWebViewBridge.sendEvent) {
        window.ReactNativeWebViewBridge.sendEvent('MY_CUSTOM_ACTION', { data: '一些数据', timestamp: Date.now() });
      } else {
        console.warn('ReactNativeWebViewBridge.sendEvent 不可用。');
      }
    }

    // 由 React Native 的 injectJavaScript 调用的函数
    window.receiveDataFromRN = function(data) {
      console.log('Web: 收到来自 RN 的数据:', data);
      alert('来自 RN 的消息: ' + data.content);
    };
  </script>
</head>
<body>
  <button onclick="requestNavigation()">前往个人资料 (RN)</button>
  <button onclick="requestShare()">分享此页面 (原生)</button>
  <button onclick="sendCustomEvent()">向 RN 发送自定义事件</button>
</body>
</html>

###主要 Props

Prop 类型 默认值 描述
uri string 必需 要加载的网页 URL。
onMessage (message: { type: string; payload?: any }) => void 必需 回调函数,用于处理通过桥接从 WebView 的 JavaScript 发送的消息。
authToken string undefined 可选的认证令牌,将包含在 Authorization 头部中。
acceptLanguage string undefined 可选的 Accept-Language 头部值。
additionalHeaders Record<string, string> {} 随初始请求一起发送的任何其他自定义 HTTP 头部。
injectedJavaScriptBeforeBridge string undefined window.ReactNativeWebViewBridge 设置之前注入的自定义 JavaScript 字符串。
injectedJavaScriptAfterBridge string undefined window.ReactNativeWebViewBridge 设置之后注入的自定义 JavaScript 字符串。
baseInjectedJavaScript string 内部桥接脚本 如果需要高级定制,允许覆盖整个默认桥接脚本。
showsLoadingIndicator boolean true 页面加载时是否显示默认加载指示器。
customLoadingComponent React.ReactNode undefined 作为加载指示器显示的自定义 React 组件。
style ViewStyle { flex: 1 } WebView 容器的样式。
onShouldStartLoadWithRequest (event: WebViewNavigationEvent) => boolean undefined (传递给 react-native-webview) 函数,允许您拦截 Web 请求并决定是在 WebView 中加载它们还是以不同方式处理它们(例如,在外部打开)。
... WebViewProps 其他标准的 react-native-webview props 会被传递。

###暴露的 Ref 方法 (WebViewRef)

您可以获取 CustomWebView 组件的 ref 以访问这些方法:

  • injectJavaScript(script: string): 在加载的网页中执行 JavaScript 代码。
  • reload(): 重新加载当前网页。
  • goBack(): 在 WebView 的历史记录中后退。
  • goForward(): 在 WebView 的历史记录中前进。
  • stopLoading(): 停止 WebView 加载当前页面。

繁體中文 (Traditional Chinese)

一個可複用的 React Native WebView 組件,專為實現 Web 內容與 React Native 應用程式之間的靈活互動而設計。它簡化了常見任務,例如從 Web 頁面導航到 React Native 螢幕、觸發原生分享功能以及注入 JavaScript 以進行雙向通訊。

###核心理念

CustomWebView 組件提供了一個標準化的 JavaScript 橋接 (window.ReactNativeWebViewBridge),它會自動注入到您的 Web 內容中。這個橋接允許您的 Web 應用程式的 JavaScript 輕鬆地向 React Native 應用程式發送結構化訊息,反之亦然。

###主要特性

  • 標準化 JS 橋接: 向 Web 內容注入 window.ReactNativeWebViewBridge 以方便通訊。
  • Web 到 React Native 通訊:
    • window.ReactNativeWebViewBridge.navigate(details): 請求 React Native 導航到不同的螢幕。
    • window.ReactNativeWebViewBridge.share(details): 请求 React Native 觸發原生分享對話框。
    • window.ReactNativeWebViewBridge.sendEvent(eventType, eventData): 發送帶有任意資料的自訂事件。
    • window.ReactNativeWebViewBridge.postMessage({ type: "...", payload: ... }): 所有橋接通訊的底層方法。
  • React Native 到 Web 通訊:
    • 使用組件的 ref 呼叫 injectJavaScript("yourJSCode(); true;") 在 WebView 內執行 JavaScript。
  • 可自訂:
    • 傳遞 uri 載入任何網頁。
    • 透過 onMessage prop 處理來自 Web 的訊息。
    • 在主橋接設定之前或之後注入自訂 JavaScript。
    • 設定自訂 HTTP 標頭 (例如,用於身份驗證)。
    • 控制載入指示器。
  • 暴露 WebView 控制項: 透過其 ref 提供對常見 WebView 方法的存取,如 reload()goBack() 等。

###如何使用 (React Native 端)

  1. 匯入組件:

    import CustomWebView, { WebViewRef } from './path/to/your/CustomWebView'; // 調整路徑
    import { useRef } from 'react';
    import { useNavigation } from '@react-navigation/native'; // 或您的導航庫
    import { Share, Alert } from 'react-native';
  2. 在您的組件中使用它:

    const MyScreenWithWebView = () => {
      const webViewRef = useRef<WebViewRef>(null);
      const navigation = useNavigation();
    
      const handleWebViewMessage = (message) => {
        console.log('RN: 收到訊息:', message.type, message.payload);
        switch (message.type) {
          case 'NAVIGATE':
            if (message.payload?.routeName) {
              navigation.navigate(message.payload.routeName, message.payload.params);
            }
            break;
          case 'SHARE':
            if (message.payload) {
              Share.share({
                message: message.payload.message || '',
                url: message.payload.url,
                title: message.payload.title,
              });
            }
            break;
          case 'CUSTOM_EVENT_FROM_WEB':
            Alert.alert('自訂事件', `收到: ${JSON.stringify(message.payload)}`);
            break;
          // ... 處理其他訊息類型
        }
      };
    
      const sendDataToWebView = () => {
        if (webViewRef.current) {
          const script = `
            if (window.receiveDataFromRN) {
              window.receiveDataFromRN({ content: '來自 React Native 的問候!' });
            }
            true; // 重要!
          `;
          webViewRef.current.injectJavaScript(script);
        }
      };
    
      return (
        <CustomWebView
          ref={webViewRef}
          uri="https://your-web-app.com/page.html" // 替換為您的 Web 應用程式 URL
          onMessage={handleWebViewMessage}
          // 可選 props
          authToken="your_auth_token_if_needed" // 如果需要,傳入認證 token
          showsLoadingIndicator={true}
          onShouldStartLoadWithRequest={(request) => {
            // 控制導航: 返回 true 允許載入, false 阻止
            // 或在系統瀏覽器中開啟: Linking.openURL(request.url); return false;
            console.log('嘗試載入:', request.url);
            return true;
          }}
        />
        // <Button title="傳送到 Web" onPress={sendDataToWebView} />
      );
    };

###如何使用 (Web 頁面端 - 供 Web 開發人員使用)

您的 Web 應用程式的 JavaScript 可以使用 window.ReactNativeWebViewBridge 物件與 React Native 應用程式進行互動。

<!DOCTYPE html>
<html>
<head>
  <title>Web 頁面</title>
  <script>
    function requestNavigation() {
      if (window.ReactNativeWebViewBridge && window.ReactNativeWebViewBridge.navigate) {
        window.ReactNativeWebViewBridge.navigate({ routeName: 'UserProfile', params: { id: '123' } });
      } else {
        console.warn('ReactNativeWebViewBridge.navigate 不可用。');
      }
    }

    function requestShare() {
      if (window.ReactNativeWebViewBridge && window.ReactNativeWebViewBridge.share) {
        window.ReactNativeWebViewBridge.share({
          url: window.location.href,
          message: '快來看看這個頁面!',
          title: document.title
        });
      } else {
        console.warn('ReactNativeWebViewBridge.share 不可用。');
      }
    }

    function sendCustomEvent() {
      if (window.ReactNativeWebViewBridge && window.ReactNativeWebViewBridge.sendEvent) {
        window.ReactNativeWebViewBridge.sendEvent('MY_CUSTOM_ACTION', { data: '一些資料', timestamp: Date.now() });
      } else {
        console.warn('ReactNativeWebViewBridge.sendEvent 不可用。');
      }
    }

    // 由 React Native 的 injectJavaScript 呼叫的函數
    window.receiveDataFromRN = function(data) {
      console.log('Web: 收到來自 RN 的資料:', data);
      alert('來自 RN 的訊息: ' + data.content);
    };
  </script>
</head>
<body>
  <button onclick="requestNavigation()">前往個人資料 (RN)</button>
  <button onclick="requestShare()">分享此頁面 (原生)</button>
  <button onclick="sendCustomEvent()">向 RN 發送自訂事件</button>
</body>
</html>

###主要 Props

Prop 類型 預設值 描述
uri string 必需 要載入的網頁 URL。
onMessage (message: { type: string; payload?: any }) => void 必需 回呼函數,用於處理透過橋接從 WebView 的 JavaScript 發送的訊息。
authToken string undefined 可選的認證權杖,將包含在 Authorization 標頭中。
acceptLanguage string undefined 可選的 Accept-Language 標頭值。
additionalHeaders Record<string, string> {} 隨初始請求一起發送的任何其他自訂 HTTP 標頭。
injectedJavaScriptBeforeBridge string undefined window.ReactNativeWebViewBridge 設定之前注入的自訂 JavaScript 字串。
injectedJavaScriptAfterBridge string undefined window.ReactNativeWebViewBridge 設定之後注入的自訂 JavaScript 字串。
baseInjectedJavaScript string 內部橋接腳本 如果需要進階自訂,允許覆寫整個預設橋接腳本。
showsLoadingIndicator boolean true 頁面載入時是否顯示預設載入指示器。
customLoadingComponent React.ReactNode undefined 作為載入指示器顯示的自訂 React 組件。
style ViewStyle { flex: 1 } WebView 容器的樣式。
onShouldStartLoadWithRequest (event: WebViewNavigationEvent) => boolean undefined (傳遞給 react-native-webview) 函數,允許您攔截 Web 請求並決定是在 WebView 中載入它們還是以不同方式處理它們(例如,在外部開啟)。
... WebViewProps 其他標準的 react-native-webview props 會被傳遞。

###暴露的 Ref 方法 (WebViewRef)

您可以獲取 CustomWebView 組件的 ref 以存取這些方法:

  • injectJavaScript(script: string): 在載入的網頁中執行 JavaScript 程式碼。
  • reload(): 重新載入目前網頁。
  • goBack(): 在 WebView 的歷史記錄中後退。
  • goForward(): 在 WebView 的歷史記錄中前進。
  • stopLoading(): 停止 WebView 載入目前頁面。
// CustomWebView.tsx (Simplified Conceptual Outline)
import React, { forwardRef, useImperativeHandle, useRef, useState, useMemo, useEffect } from 'react';
import { ActivityIndicator, Platform, StyleSheet, View, ViewStyle } from 'react-native';
import WebView, { WebViewProps, WebViewMessageEvent, WebViewNavigation } from 'react-native-webview';
import DeviceInfo from 'react-native-device-info'; // Can be kept if generally useful (e.g. for version header)
// Default JavaScript bridge injected into the WebView
const DEFAULT_BASE_INJECTED_JAVASCRIPT = `
// Create a namespace to avoid polluting the global window object too much
window.ReactNativeWebViewBridge = {
/**
* Posts a message to the React Native application.
* @param {object} message - The message object, should have a 'type' and 'payload'.
*/
postMessage: function(message) {
if (window.ReactNativeWebView && window.ReactNativeWebView.postMessage) {
window.ReactNativeWebView.postMessage(JSON.stringify(message));
} else {
// console.warn('ReactNativeWebView.postMessage is not available. Message:', message);
}
},
/**
* Requests navigation within the React Native app.
* @param {object} details - Navigation details (e.g., { routeName: 'Screen', params: {} }).
*/
navigate: function(details) {
this.postMessage({ type: 'NAVIGATE', payload: details });
},
/**
* Requests to trigger native sharing.
* @param {object} details - Share details (e.g., { url: '', message: '', title: '' }).
*/
share: function(details) {
this.postMessage({ type: 'SHARE', payload: details });
},
/**
* Sends a generic event to React Native.
* @param {string} eventType - The type of the event.
* @param {any} eventData - The data associated with the event.
*/
sendEvent: function(eventType, eventData) {
this.postMessage({ type: eventType, payload: eventData });
}
};
true; // Required for injectedJavaScript to work reliably
`;
// Interface for the ref methods exposed by CustomWebView
export interface WebViewRef {
injectJavaScript: (script: string) => void;
reload: () => void;
goBack: () => void;
goForward: () => void;
stopLoading: () => void;
// You can add more methods from react-native-webview if needed
}
// Props for the CustomWebView component
export interface CustomWebViewProps extends Omit<WebViewProps, 'onMessage' | 'source' | 'injectedJavaScript'> {
uri: string; // The URI to load
onMessage: (message: { type: string; payload?: any }) => void; // Callback for messages from WebView
authToken?: string; // Optional token for Authorization header
acceptLanguage?: string; // Optional Accept-Language header (e.g., "en-US,en;q=0.5")
additionalHeaders?: Record<string, string>; // Any other custom headers
// JS to inject *before* the main bridge is set up
injectedJavaScriptBeforeBridge?: string;
// JS to inject *after* the main bridge (window.ReactNativeWebViewBridge) is set up
injectedJavaScriptAfterBridge?: string;
// The core JS bridge script. Defaults to the one setting up window.ReactNativeWebViewBridge.
// Advanced users could override this for a completely custom bridge.
baseInjectedJavaScript?: string;
showsLoadingIndicator?: boolean; // Whether to show a default loading indicator
customLoadingComponent?: React.ReactNode; // Provide a custom loading component
// Standard WebView props that are useful to expose directly
style?: ViewStyle;
scrollEnabled?: boolean;
// onShouldStartLoadWithRequest is part of WebViewProps and very important for control
// onError, onLoadStart, onLoadEnd are also part of WebViewProps
}
const CustomWebView = forwardRef<WebViewRef, CustomWebViewProps>((props, ref) => {
const {
uri,
onMessage: onMessagePropFromParent, // Renamed to avoid conflict
authToken,
acceptLanguage,
additionalHeaders = {},
injectedJavaScriptBeforeBridge,
injectedJavaScriptAfterBridge,
baseInjectedJavaScript = DEFAULT_BASE_INJECTED_JAVASCRIPT,
showsLoadingIndicator = true,
customLoadingComponent,
onLoadStart: onLoadStartProp,
onLoadEnd: onLoadEndProp,
style: propStyle,
// Collect remaining WebViewProps to pass them through
...restWebViewProps
} = props;
const internalWebViewRef = useRef<WebView>(null);
const [isLoading, setIsLoading] = useState(true); // Internal loading state for the indicator
// Expose methods to the parent component via ref
useImperativeHandle(ref, () => ({
injectJavaScript: (script: string) => {
internalWebViewRef.current?.injectJavaScript(script);
},
reload: () => {
internalWebViewRef.current?.reload();
},
goBack: () => {
internalWebViewRef.current?.goBack();
},
goForward: () => {
internalWebViewRef.current?.goForward();
},
stopLoading: () => {
internalWebViewRef.current?.stopLoading();
},
}));
// Memoize headers to prevent unnecessary re-renders if props don't change
const requestHeaders = useMemo(() => {
const h: Record<string, string> = { ...additionalHeaders };
if (authToken) {
h['Authorization'] = `Bearer ${authToken}`;
}
if (acceptLanguage) {
h['Accept-Language'] = acceptLanguage;
}
// Example of a generally useful header you might always want to include
h['X-App-Version'] = DeviceInfo.getVersion();
h['X-Platform'] = Platform.OS;
return h;
}, [authToken, acceptLanguage, additionalHeaders]);
// Combine all JavaScript to be injected
const finalInjectedJavaScript = useMemo(() => {
return `
${injectedJavaScriptBeforeBridge || ''}
${baseInjectedJavaScript}
${injectedJavaScriptAfterBridge || ''}
`;
}, [injectedJavaScriptBeforeBridge, baseInjectedJavaScript, injectedJavaScriptAfterBridge]);
// Handle messages from the WebView
const handleWebViewMessage = (event: WebViewMessageEvent) => {
try {
const nativeEventData = event.nativeEvent.data;
const parsedMessage = JSON.parse(nativeEventData);
// Ensure it's an object with a 'type' property as per our bridge's convention
if (typeof parsedMessage === 'object' && parsedMessage !== null && typeof parsedMessage.type === 'string') {
onMessagePropFromParent(parsedMessage as { type: string; payload?: any });
} else {
console.warn('RN CustomWebView: Received non-standard message from WebView:', nativeEventData);
// Optionally, you could pass on raw string messages if a parent needs to handle them:
// onMessagePropFromParent({ type: 'RAW_STRING_MESSAGE', payload: nativeEventData });
}
} catch (error) {
console.error('RN CustomWebView: Error parsing message from WebView:', error, event.nativeEvent.data);
// Notify parent about the parse error if needed
// onMessagePropFromParent({ type: 'MESSAGE_PARSE_ERROR', payload: event.nativeEvent.data });
}
};
const handleLoadStart = (syntheticEvent: WebViewNavigation) => {
setIsLoading(true);
if (onLoadStartProp) {
onLoadStartProp(syntheticEvent); // Forward to parent's onLoadStart
}
};
const handleLoadEnd = (syntheticEvent: WebViewNavigation) => {
setIsLoading(false);
if (onLoadEndProp) {
onLoadEndProp(syntheticEvent); // Forward to parent's onLoadEnd
}
};
// Render a loading indicator
const renderLoadingIndicator = () => {
if (!isLoading || !showsLoadingIndicator) return null;
if (customLoadingComponent) return customLoadingComponent;
return (
<View style={styles.loadingOverlay}>
<ActivityIndicator size="large" color="#0000ff" />
</View>
);
};
// Key to force re-render WebView if URI or critical params change in a way WebView doesn't auto-handle
// Useful if headers that affect initial load (like auth token) change.
const webViewKey = useMemo(() => `${uri}-${authToken}`, [uri, authToken]);
return (
<View style={[styles.container, propStyle]}>
<WebView
key={webViewKey} // Force re-mount if uri or token changes
ref={internalWebViewRef}
source={{ uri, headers: requestHeaders }}
onMessage={handleWebViewMessage}
injectedJavaScript={finalInjectedJavaScript}
onLoadStart={handleLoadStart}
onLoadEnd={handleLoadEnd}
// Default props that are generally good for a reusable component
originWhitelist={['*']} // Consider making this a prop for better security control
javaScriptEnabled={true}
domStorageEnabled={true}
sharedCookiesEnabled={true} // If cookies need to be shared with system browser/other WebViews
thirdPartyCookiesEnabled={true} // If your web content relies on third-party cookies
allowsInlineMediaPlayback={true} // For video playback within the WebView
// Pass through other standard react-native-webview props provided by the parent
{...restWebViewProps}
/>
{renderLoadingIndicator()}
</View>
);
});
const styles = StyleSheet.create({
container: {
flex: 1, // Ensure the WebView container takes up space
position: 'relative', // For absolute positioning of loading overlay
},
loadingOverlay: {
...StyleSheet.absoluteFillObject, // Cover the entire WebView
backgroundColor: 'rgba(255, 255, 255, 0.7)',
justifyContent: 'center',
alignItems: 'center',
zIndex: 10, // Ensure it's on top
},
});
export default CustomWebView;
// In your React Native Screen (e.g., MyScreenWithWebView.tsx)
import React, { useRef } from 'react';
import { View, StyleSheet, Button, Share, Alert } from 'react-native';
import { useNavigation } from '@react-navigation/native'; // Or your navigation library
import CustomWebView, { WebViewRef } from './path/to/your/CustomWebView'; // Adjust path
const MyScreenWithWebView = () => {
const webViewRef = useRef<WebViewRef>(null);
const navigation = useNavigation(); // Hook from your navigation library
// This function handles messages sent from the WebView's JavaScript
const handleWebViewMessage = (message: { type: string; payload?: any }) => {
console.log('RN: Received message from WebView:', message.type, message.payload);
switch (message.type) {
case 'NAVIGATE':
if (message.payload?.routeName) {
console.log(`RN: Navigating to ${message.payload.routeName}`);
// @ts-ignore - Adjust based on your navigation setup
navigation.navigate(message.payload.routeName, message.payload.params);
} else if (message.payload?.url) {
console.log(`RN: Handling navigation URL ${message.payload.url}`);
// Handle deep linking or other URL-based navigation
// Linking.openURL(message.payload.url);
} else {
console.warn('RN: NAVIGATE event received with invalid payload', message.payload);
}
break;
case 'SHARE':
if (message.payload) {
console.log('RN: Initiating share');
Share.share({
message: message.payload.message || '',
url: message.payload.url, // URL is often the most important for web content
title: message.payload.title,
})
.then(result => console.log('RN: Share successful', result))
.catch(error => console.error('RN: Share error:', error));
} else {
console.warn('RN: SHARE event received with invalid payload', message.payload);
}
break;
case 'UPDATE_APP_HEADER':
if (message.payload?.title) {
console.log(`RN: Setting header title to "${message.payload.title}"`);
// Example: Update navigation header. Actual implementation depends on your nav library.
// navigation.setOptions({ title: message.payload.title });
Alert.alert("Header Update Request", `Web wants to set title to: ${message.payload.title}`);
}
break;
// Add more cases for other custom event types your web app might send
default:
console.log('RN: Unhandled message type from WebView:', message.type);
}
};
// Function to inject JavaScript into the WebView
const sendMessageToWebView = () => {
if (webViewRef.current) {
const dataToSend = { message: "Hello from React Native!", timestamp: Date.now() };
// This script calls the 'window.updateFromReactNative' function defined in the web page
const script = `
if (window.updateFromReactNative) {
window.updateFromReactNative(${JSON.stringify(dataToSend)});
} else {
console.warn('Web: window.updateFromReactNative function not found.');
}
true; // Important for Android: injectedJavaScript must return a value, typically true.
`;
console.log("RN: Injecting script into WebView:", script);
webViewRef.current.injectJavaScript(script);
} else {
console.warn("RN: WebView ref not available to inject JavaScript.");
}
};
return (
<View style={styles.container}>
<Button title="Send Message to Web Page" onPress={sendMessageToWebView} />
<CustomWebView
ref={webViewRef}
uri="https://your-web-app-url.com/your-page.html" // Replace with your actual web app URL
onMessage={handleWebViewMessage}
// Optional: Pass an auth token if your web app needs it in headers
// authToken="YOUR_SECURE_AUTH_TOKEN"
// Optional: Control how links are handled
onShouldStartLoadWithRequest={(request) => {
console.log('RN: WebView attempting to load URL:', request.url);
// Example: Open external links in the system browser
// if (!request.url.startsWith('https://your-web-app-url.com')) {
// Linking.openURL(request.url);
// return false; // Prevent WebView from loading it
// }
return true; // Allow WebView to load the URL
}}
// You can add more props like style, onLoadEnd, onError etc.
/>
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
},
// Add other styles as needed
});
export default MyScreenWithWebView;
<!DOCTYPE html>
<html>
<head>
<title>My Interactive Web Page</title>
<script>
function goToProfile() {
if (window.ReactNativeWebViewBridge && window.ReactNativeWebViewBridge.navigate) {
console.log("Web: Requesting navigation to ProfileScreen");
window.ReactNativeWebViewBridge.navigate({ routeName: 'ProfileScreen', params: { userId: 'webUser456' } });
} else {
console.warn('ReactNativeWebViewBridge.navigate is not available.');
// Fallback for browsers or if bridge isn't ready
// alert('Navigate to profile (fallback)');
}
}
function sharePage() {
if (window.ReactNativeWebViewBridge && window.ReactNativeWebViewBridge.share) {
console.log("Web: Requesting to share current page");
window.ReactNativeWebViewBridge.share({
url: window.location.href,
message: 'Check out this page: ' + document.title,
title: document.title
});
} else {
console.warn('ReactNativeWebViewBridge.share is not available.');
// Fallback
// alert('Share this page (fallback)');
}
}
function updateAppHeader() {
if (window.ReactNativeWebViewBridge && window.ReactNativeWebViewBridge.sendEvent) {
console.log("Web: Requesting to update app header title");
window.ReactNativeWebViewBridge.sendEvent('UPDATE_APP_HEADER', { title: 'Dynamic Title from Web' });
} else {
console.warn('ReactNativeWebViewBridge.sendEvent is not available.');
}
}
// Example of receiving data from React Native
window.updateFromReactNative = function(data) {
console.log("Web: Received data from React Native:", data);
document.getElementById('rnMessage').innerText = 'Message from RN: ' + data.message;
};
</script>
</head>
<body>
<h1>Interact with React Native</h1>
<button onclick="goToProfile()">Go to My Profile (RN Screen)</button>
<button onclick="sharePage()">Share This Page (Native Share)</button>
<button onclick="updateAppHeader()">Update RN Header Title</button>
<p id="rnMessage"></p>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment