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-webviewinstalled - 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
Step 2: Basic WebView Setup
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:
// 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
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:
// 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
- Go to Actions → Create new action → Client Action
- Enter an Action name (e.g., "Update profile")
- Add optional Description with trigger examples
- 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
originWhitelistto limit allowed domains - Validate all inputs on the native side
- Store auth tokens securely in native code
Testing Your Implementation
- Set up a test Agent in Brainfish
- Create test action with "Show on" filter for test Agent only
- Test in staging environment first
- Verify scenarios:
- Success cases
- Network failures
- Validation errors
- Timeout handling
- 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.ReactNativeWebViewexists - 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
- Review react-native-webview documentation
- Contact Brainfish support for assistance
