---
title: "How to implement Client Actions in a React Native app (via WebView) [ALPHA]"
description: "Client actions allow your React Native app to communicate with the Brainfish widget when it's embedded in a WebView. This guide shows you two clear implementation options."
canonical_url: "https://help.brainfi.sh/articles/how-to-implement-client-actions-in-a-react-native-app-via-webview-alpha-w0ZVYGx1PI"
md_url: "https://help.brainfi.sh/articles/how-to-implement-client-actions-in-a-react-native-app-via-webview-alpha-w0ZVYGx1PI.md"
---
# How to implement Client Actions in a React Native app (via WebView) [ALPHA]

Client actions allow your React Native app to communicate with the Brainfish widget when it's embedded in a WebView. This guide shows you two clear implementation options.

## Prerequisites

* React Native app with `react-native-webview` installed
* Brainfish Agent widget rendered inside the WebView
* A Client Action created in Brainfish (with the Action Key copied)

## Two Implementation Options

### **Option 1: Pure Web Implementation**

Run the action logic entirely within the WebView (e.g., calling APIs with `fetch`).

### **Option 2: Native-Bridged Implementation (Recommended)**

Forward actions to React Native for native capabilities, then return results to the WebView.


---

## Option 1: Pure Web Implementation

Handle everything directly in your web page without native code interaction.

### Step 1: Set Up Your Client Action

Follow this guide to configure your action in Brainfish: [Setting up a Client Action](https://help.brainfi.sh/en-us/articles/guide-setting-up-a-client-action-beta-XTj68IARfY?blk=true)

### Step 2: Basic WebView Setup

```javascript
import React from 'react';
import { SafeAreaView } from 'react-native';
import { WebView } from 'react-native-webview';

export default function App() {
  return (
    <SafeAreaView style={{ flex: 1 }}>
      <WebView 
        source={{ uri: 'https://your-website.com' }}
        javaScriptEnabled={true}
        style={{ flex: 1 }}
      />
    </SafeAreaView>
  );
}
```

### Step 3: Register Handler in Your Web Page

Add this to your web page where the Brainfish widget loads:

```javascript
// Pure web implementation - everything happens in the browser
Brainfish.Widgets.registerClientActionHandler('bf_action_abc123', async (inputs) => {
  // Get the validated inputs
  const { email, phone, username } = inputs;
  
  // Call your API directly from the web page
  const response = await fetch('https://api.yourapp.com/me', {
    method: 'POST',
    headers: { 
      'Content-Type': 'application/json',
      'Authorization': 'Bearer ' + window.authToken 
    },
    body: JSON.stringify({ email, phone, username })
  });
  
  if (!response.ok) {
    throw new Error('Failed to update account');
  }
  
  const { updatedAt } = await response.json();
  return { updatedAt }; // Must return an object
});
```


---

## Option 2: Native-Bridged Implementation (Recommended)

Use this when you need native capabilities like device SDKs, secure storage, or native modules.

### Step 1: React Native Side - Set Up Bridge

```javascript
import React, { useRef, useCallback } from 'react';
import { SafeAreaView } from 'react-native';
import WebView from 'react-native-webview';

export default function AppWebView() {
  const webRef = useRef(null);

  // Send success response back to web
  const resolveToWeb = useCallback((id, payload) => {
    const js = `
      window.__bfResolveAction && window.__bfResolveAction(${JSON.stringify({
        id,
        ok: true,
        payload,
      })});
      true;
    `;
    webRef.current?.injectJavaScript(js);
  }, []);

  // Send error response back to web
  const rejectToWeb = useCallback((id, error) => {
    const js = `
      window.__bfResolveAction && window.__bfResolveAction(${JSON.stringify({
        id,
        ok: false,
        error,
      })});
      true;
    `;
    webRef.current?.injectJavaScript(js);
  }, []);

  // Handle messages from WebView
  const handleMessage = useCallback(async (event) => {
    try {
      const msg = JSON.parse(event.nativeEvent.data);
      if (msg?.type !== 'brainfish:action') return;

      const { id, actionKey, inputs } = msg;

      // Route actions by their key
      switch (actionKey) {
        case 'bf_action_abc123':
          try {
            // Your native logic here
            const result = await updateUserProfile(inputs);
            resolveToWeb(id, { updatedAt: result.updatedAt });
          } catch (err) {
            rejectToWeb(id, err.message);
          }
          break;

        default:
          rejectToWeb(id, `Unknown action: ${actionKey}`);
      }
    } catch (err) {
      console.warn('Bridge error:', err);
    }
  }, [resolveToWeb, rejectToWeb]);

  // Inject bridge setup code
  const injectedJavaScript = `
    (function(){
      const pending = new Map();

      // Generate unique ID
      function uuid(){ 
        return Math.random().toString(36).slice(2) + Date.now(); 
      }

      // React Native calls this to resolve/reject
      window.__bfResolveAction = ({ id, ok, payload, error }) => {
        const p = pending.get(id);
        if (!p) return;
        pending.delete(id);
        ok ? p.resolve(payload) : p.reject(new Error(error || 'Action failed'));
      };

      // Create a handler that forwards to React Native
      window.__bfMakeNativeBackedHandler = function(actionKey){
        return function(inputs){
          const id = uuid();
          
          // Send to React Native
          window.ReactNativeWebView?.postMessage(JSON.stringify({
            type: 'brainfish:action',
            id,
            actionKey,
            inputs
          }));
          
          // Wait for response (with 5s timeout)
          return new Promise((resolve, reject) => {
            const timeout = setTimeout(() => {
              pending.delete(id);
              reject(new Error('Action timed out'));
            }, 4900);
            
            pending.set(id, {
              resolve: (v) => { 
                clearTimeout(timeout); 
                resolve(v || {}); 
              },
              reject: (e) => { 
                clearTimeout(timeout); 
                reject(e); 
              }
            });
          });
        };
      };
    })();
    true;
  `;

  return (
    <SafeAreaView style={{ flex: 1 }}>
      <WebView
        ref={webRef}
        source={{ uri: 'https://your-website.com' }}
        onMessage={handleMessage}
        injectedJavaScript={injectedJavaScript}
        javaScriptEnabled={true}
        originWhitelist={['https://*']}
        style={{ flex: 1 }}
      />
    </SafeAreaView>
  );
}

// Example native function
async function updateUserProfile({ email, phone, username }) {
  // Call native modules, secure APIs, etc.
  // Must complete within ~4.5 seconds
  const response = await fetch('https://api.yourapp.com/me', {
    method: 'POST',
    headers: { 
      'Authorization': await getSecureToken() // Native secure storage
    },
    body: JSON.stringify({ email, phone, username })
  });
  
  return { updatedAt: new Date().toISOString() };
}
```

### Step 2: Register Handler in Your Web Page

Add this to your web page where Brainfish loads:

```javascript
// Register the native-backed handler
Brainfish.Widgets.registerClientActionHandler(
  'bf_action_abc123',
  window.__bfMakeNativeBackedHandler('bf_action_abc123')
);

// Register multiple actions if needed
Brainfish.Widgets.registerClientActionHandler(
  'bf_action_xyz',
  window.__bfMakeNativeBackedHandler('bf_action_xyz')
);
```


---

## Setting Up Your Client Action in Brainfish

### Step 1: Define Your Action


1. Go to **Actions** → **Create new action** → **Client Action**
2. Enter an **Action name** (e.g., "Update profile")
3. Add optional **Description** with trigger examples
4. Click **Configure your inputs**

### Step 2: Configure Inputs

Add the inputs your action needs:

* **Input Name**: Internal variable name (e.g., `email`)
* **Required**: Toggle if this input is mandatory
* **Input Type**: Choose from Short Answer, Email, Phone, etc.
* **Label**: User-friendly label for the form

### Step 3: Apply Filters

* **Show on**: Start with a test Agent only
* Click **Save & Publish**

### Step 4: Copy Your Action Key

Click the context menu → **Copy Action Key** (e.g., `bf_action_abc123`)


---

## Quick Decision Guide

**Choose Option 1 (Pure Web) if:**

* You only need to call web APIs
* No native features required
* Simpler implementation preferred

**Choose Option 2 (Native-Bridged) if:**

* You need native device capabilities
* Secure token storage required
* Complex native business logic
* Better error handling needed


---

## Important Notes

### ⏱️ Timing Requirements

* Actions must complete within **5 seconds**
* Set timeout to \~4.9 seconds to be safe
* Keep native operations fast

### 📦 Return Values

* **Always return an object** (even `{ ok: true }`)
* Throw an Error to signal failure: `throw new Error('Reason')`
* Include enough data for the AI agent to understand the outcome

### 🔒 Security Best Practices

* Don't pass secrets through WebView messages
* Use `originWhitelist` to limit allowed domains
* Validate all inputs on the native side
* Store auth tokens securely in native code


---

## Testing Your Implementation


1. **Set up a test Agent** in Brainfish
2. **Create test action** with "Show on" filter for test Agent only
3. **Test in staging** environment first
4. **Verify scenarios:**
   * Success cases
   * Network failures
   * Validation errors
   * Timeout handling
5. **Remove filters** when ready for production


---

## Troubleshooting

### Action not triggering

* Verify Action Key matches exactly
* Check handler is registered after Brainfish loads
* Ensure `javaScriptEnabled={true}` on WebView

### Messages not received

* Check `window.ReactNativeWebView` exists
* Verify message type is `'brainfish:action'`
* Add console.log debugging

### Timeout errors

* Reduce processing time to under 4.5 seconds
* Run parallel operations where possible
* Return minimal data needed


---

## Need Help?

* Check the [Brainfish Setup Guide](https://help.brainfi.sh/en-us/articles/guide-setting-up-a-client-action-beta-XTj68IARfY?blk=true)
* Review [react-native-webview documentation](https://github.com/react-native-webview/react-native-webview)
* Contact Brainfish support for assistance
