---
schema_version: "1"
id: 12aab2b7d3f160aa1540a2a6107b99ec84d559473ba70e519dd918593485335f
title: Delegates in React Native
collection: developer
version: current
locale: en-us
portal_url: https://docs.salesforce.com/developer/current/en-us/genai/agentforce-mobile-sdk/guides/react-native/agentforce-mobile-sdk-react-native-delegates.md
canonical_url: https://developer.salesforce.com/docs/ai/agentforce-mobile-sdk/guide/agentforce-mobile-sdk-react-native-delegates.html
source_site: developer.salesforce.com
content_type: documentation
content_hash: 17606b3b5baedc7aae3ca51036f5a5b576a67894e0a1b905cb38c523ee5fdc1b
products:
  - Agentforce Mobile SDK
  - Agentforce and Einstein Generative AI
---
# Delegates in React Native

Learn how to use delegates to receive events and customize behavior from the Agentforce SDK in your React Native application.

## Overview

Delegates allow your JavaScript code to receive events and override behavior from the native Agentforce SDK. The bridge supports three delegate types:

| Delegate      | Purpose                                                | Direction                                            |
| ------------- | ------------------------------------------------------ | ---------------------------------------------------- |
| Logger        | Receive SDK log messages                               | Native → JS                                          |
| Navigation    | Handle navigation requests from the agent              | Native → JS                                          |
| View Provider | Override native SDK views with React Native components | JS → Native (registration) + Native → JS (rendering) |

All delegates use a `NativeEventEmitter` on the JS side to receive events from the native layer.

### Why Register Before configure()?

The recommended order is to register delegates **before** calling `configure()`. Here's why:

- **Logger**: The SDK may emit log messages during initialization. If registered after `configure()`, those early messages are lost.
- **Navigation**: The navigation bridge is passed to `AgentforceConfiguration` during `configure()`. Registering afterward still works because the bridge checks dynamically, but registering before is cleaner.
- **View Provider**: Can be registered before or after `configure()` without issue. The native view provider checks the component map dynamically.

## Logger Delegate

The Logger delegate forwards log messages from the native Agentforce SDK to your JavaScript code. This is useful for debugging, analytics, and monitoring.

### Setup

```tsx
import { AgentforceService } from "@salesforce/react-native-agentforce";

AgentforceService.setLoggerDelegate({
  onLog(level, message, error) {
    const prefix = `[Agentforce ${level.toUpperCase()}]`;
    if (error) {
      console.log(`${prefix} ${message} | Error: ${error}`);
    } else {
      console.log(`${prefix} ${message}`);
    }
  },
});
```

### Parameters

| Parameter | Type                                     | Description                                                |
| --------- | ---------------------------------------- | ---------------------------------------------------------- |
| `level`   | `'error' \| 'warn' \| 'info' \| 'debug'` | The severity level of the log message.                     |
| `message` | `string`                                 | The log message from the SDK.                              |
| `error`   | `string` (optional)                      | Stringified exception, present only for error/warn levels. |

### Clear the Delegate

```tsx
AgentforceService.clearLoggerDelegate();
```

### Platform Differences

| Platform | Log Levels Emitted               |
| -------- | -------------------------------- |
| iOS      | `error`, `warn`, `info`, `debug` |
| Android  | `error`, `warn`, `info`          |

iOS emits the `debug` level; Android doesn't. Plan your log filtering accordingly.

### Example: Analytics Forwarding

```tsx
AgentforceService.setLoggerDelegate({
  onLog(level, message, error) {
    if (level === "error") {
      analytics.track("Agentforce Error", {
        message,
        error: error || undefined,
      });
    }
  },
});
```

## Navigation Delegate

The Navigation delegate lets your app handle navigation requests from the Agentforce SDK. When the agent presents a link, record reference, or other navigable element that the user taps, the SDK emits a navigation event.

### Setup

```tsx
import { Linking } from "react-native";
import { AgentforceService } from "@salesforce/react-native-agentforce";

AgentforceService.setNavigationDelegate({
  onNavigate(request) {
    switch (request.type) {
      case "link":
        if (request.uri) {
          Linking.openURL(request.uri as string);
        }
        break;

      case "record":
        // Navigate to a record detail screen in your app
        navigation.navigate("RecordDetail", {
          recordId: request.recordId,
          objectType: request.objectType,
        });
        break;

      case "quickAction":
        console.log(`Quick action requested: ${request.actionName}`);
        break;

      case "objectHome":
        navigation.navigate("ObjectList", {
          objectType: request.objectType,
        });
        break;

      default:
        console.log("Unhandled navigation:", JSON.stringify(request));
    }
  },
});
```

### Navigation Request Types

| Type            | Description                            | Fields                                     |
| --------------- | -------------------------------------- | ------------------------------------------ |
| `record`        | Navigate to a Salesforce record        | `recordId`, `objectType`, `pageReference?` |
| `link`          | Open an external or internal URL       | `uri`, `pageReference?`                    |
| `quickAction`   | Execute a Salesforce quick action      | `actionName`, `recordId?`, `objectType?`   |
| `pageReference` | Navigate to a Lightning page reference | `pageReference`                            |
| `objectHome`    | Navigate to an object's home/list page | `objectType`, `pageReference?`             |
| `app`           | Navigate to an app or external package | `packageName`, `uri?`                      |
| `unknown`       | Catch-all for unrecognized types       | `raw`                                      |

### Clear the Delegate

```tsx
AgentforceService.clearNavigationDelegate();
```

## View Provider Delegate

The View Provider delegate allows you to replace native SDK output views with custom React Native components. When the SDK renders a component type that matches your registered map, your React Native component is rendered instead.

### Setup

There are two parts to registering a custom view:

**Part 1: Register the React Native component with AppRegistry**

```tsx
import { AppRegistry } from "react-native";
import { ViewProviderComponentData } from "@salesforce/react-native-agentforce";

function CustomRichTextView({ componentData }: { componentData: ViewProviderComponentData }) {
  const text = componentData.properties.text as string;
  return (
    <View style={{ padding: 12 }}>
      <Text style={{ fontSize: 16 }}>{text}</Text>
    </View>
  );
}

// Register it with AppRegistry
AppRegistry.registerComponent("CustomRichTextView", () => CustomRichTextView);
```

**Part 2: Register the component map with the bridge**

```tsx
await AgentforceService.setViewProviderDelegate({
  componentMap: {
    "copilot/richText": "CustomRichTextView",
    "copilot/markdown": "CustomMarkdownView",
  },
});
```

The keys are SDK component definition strings. The values are the names you registered with `AppRegistry.registerComponent()`.

### Complete Custom View Provider Example

This example shows a full custom view component that handles all properties and sub-components:

```tsx
// CustomAgentforceView.tsx
import React from "react";
import { View, Text, ScrollView, StyleSheet } from "react-native";
import type { ViewProviderComponentData } from "@salesforce/react-native-agentforce";

interface Props {
  componentData: ViewProviderComponentData;
}

function CustomAgentforceView({ componentData }: Props) {
  const { definition, name, properties, subComponents } = componentData;

  return (
    <View style={styles.container}>
      <View style={styles.header}>
        <Text style={styles.defLabel}>{definition}</Text>
        {name && <Text style={styles.nameLabel}>{name}</Text>}
      </View>

      <ScrollView style={styles.propsContainer}>
        {Object.entries(properties).map(([key, value]) => (
          <View key={key} style={styles.propRow}>
            <Text style={styles.propKey}>{key}:</Text>
            <Text style={styles.propValue}>
              {typeof value === "object" ? JSON.stringify(value) : String(value)}
            </Text>
          </View>
        ))}
      </ScrollView>

      {subComponents && subComponents.length > 0 && (
        <View style={styles.subSection}>
          <Text style={styles.subTitle}>Sub-Components ({subComponents.length})</Text>
          {subComponents.map((sub, index) => (
            <View key={index} style={styles.subItem}>
              <Text style={styles.subDef}>{sub.definition}</Text>
              <Text style={styles.subProps}>{JSON.stringify(sub.properties, null, 2)}</Text>
            </View>
          ))}
        </View>
      )}
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    margin: 8,
    padding: 12,
    backgroundColor: "#f8f9fa",
    borderRadius: 8,
    borderWidth: 1,
    borderColor: "#dee2e6",
  },
  header: { marginBottom: 8 },
  defLabel: { fontSize: 14, fontWeight: "bold", color: "#0176D3" },
  nameLabel: { fontSize: 12, color: "#666", marginTop: 2 },
  propsContainer: { maxHeight: 200 },
  propRow: { flexDirection: "row", marginBottom: 4 },
  propKey: { fontWeight: "600", marginRight: 8, color: "#333" },
  propValue: { flex: 1, color: "#555" },
  subSection: { marginTop: 12, paddingTop: 8, borderTopWidth: 1, borderTopColor: "#eee" },
  subTitle: { fontWeight: "bold", marginBottom: 4 },
  subItem: { marginLeft: 12, marginBottom: 8 },
  subDef: { fontWeight: "600", fontSize: 12 },
  subProps: { fontFamily: "monospace", fontSize: 10, color: "#666" },
});

export default CustomAgentforceView;
```

**Register the component and mapping in your App.tsx:**

```tsx
import { AppRegistry } from "react-native";
import CustomAgentforceView from "./CustomAgentforceView";
import { AgentforceService } from "@salesforce/react-native-agentforce";

// Register the component with AppRegistry -- this makes it available
// for the native bridge to render via RCTRootView / ReactRootView.
AppRegistry.registerComponent("CustomAgentforceView", () => CustomAgentforceView);

// Register the mapping with the bridge
async function setupCustomViews() {
  await AgentforceService.setViewProviderDelegate({
    componentMap: {
      "copilot/richText": "CustomAgentforceView",
      "copilot/markdown": "CustomAgentforceView",
      "copilot/recordInfo": "CustomAgentforceView",
      "copilot/list": "CustomAgentforceView",
    },
  });
}

// Call during app initialization
setupCustomViews();
```

### Component Data Interface

```tsx
interface ViewProviderComponentData {
  definition: string; // Component definition (e.g. 'copilot/richText')
  name?: string; // Component name from SDK (may be null)
  properties: Record<string, unknown>; // Key-value properties
  subComponents?: ViewProviderComponentData[]; // Nested sub-components
}
```

### Known Component Definitions

- `copilot/list`
- `copilot/recordInfo`
- `copilot/markdown`
- `copilot/richText`

### How It Works

When you register a View Provider, the following flow occurs:

1. You register a `componentMap` mapping SDK definition strings to React Native component names.
2. The native bridge stores this map in `BridgeViewProvider`.
3. When the SDK needs to render a view, it calls `canHandle(definition)` on the view provider.
4. If the definition matches a key in the map, the provider returns `true`.
5. The SDK then calls `view()` (iOS) or `GetView()` (Android), which creates an `RCTRootView` (iOS) or `ReactRootView` (Android).
6. Your React Native component is rendered, receiving `componentData` as its initial props containing the `definition`, `name`, `properties`, and any `subComponents`.

### Feature Flag Requirement

The `enableCustomViewProvider` feature flag must be `true`:

```tsx
await AgentforceService.configure({
  type: "service",
  // ...
  featureFlags: { enableCustomViewProvider: true },
});
```

### Clear the Delegate

```tsx
await AgentforceService.clearViewProviderDelegate();
```

## Registration Timing

Register delegates **before** calling `configure()` to ensure they're active when the SDK initializes:

```tsx
// 1. Set up delegates
AgentforceService.setLoggerDelegate({ onLog: handleLog });
AgentforceService.setNavigationDelegate({ onNavigate: handleNavigation });
await AgentforceService.setViewProviderDelegate({
  componentMap: {
    /* ... */
  },
});

// 2. Configure
await AgentforceService.configure({
  /* ... */
});

// 3. Launch
await AgentforceService.launchConversation();
```

### Cleanup

Always clean up delegates when they're no longer needed:

```tsx
// In a React component:
React.useEffect(() => {
  AgentforceService.setLoggerDelegate({ onLog: handleLog });
  AgentforceService.setNavigationDelegate({ onNavigate: handleNav });

  return () => {
    AgentforceService.clearLoggerDelegate();
    AgentforceService.clearNavigationDelegate();
  };
}, []);
```

Or call `destroy()` on app shutdown to clean up everything:

```tsx
AgentforceService.destroy();
```

## See Also

- [Configure the SDK](https://developer.salesforce.com/docs/ai/agentforce-mobile-sdk/guide/agentforce-mobile-sdk-react-native-integration.md)
- [Conversations](https://developer.salesforce.com/docs/ai/agentforce-mobile-sdk/guide/agentforce-mobile-sdk-react-native-conversations.md)
- [Limitations](https://developer.salesforce.com/docs/ai/agentforce-mobile-sdk/guide/agentforce-mobile-sdk-react-native-limitations.md)
