> For the complete documentation index, see [llms.txt](https://even-financial.gitbook.io/developer-center/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://even-financial.gitbook.io/developer-center/marketplace-integrations/mobile-sdks/personal-loans/react-native.md).

# React Native

Welcome to Engine by MoneyLion’s Personal Loans React Native SDK Integration Guide. This guide covers software requirements, installing and initializing the SDK, and leveraging its customizable features to optimize the user journey. Finally, there are sample screenshots of how the SDK looks (which may vary depending on your color customizations).

{% hint style="success" %}
This integration renders Engine's screens natively and supports color customization.
{% endhint %}

## Setup Instructions

#### Step 1: Request an API token

Before you can use the Engine SDK, you’ll need to request an API token from your Partner Manager. This is used to authenticate with the Engine API and provides access to different financial institutions. You may want to use multiple API tokens depending on how many entry points your app has into the SDK.

#### Step 2: Confirm Software Requirements

Requirements:

* React Native

Package Size:

* SDK Bundle Size: 1.27MB Unpacked

#### Step 3: Install the SDK

Install the SDK from npm using the command using your package manager, e.g.

* `npm install --save @moneylion/react-native-engine-sdk`
* `yarn add @moneylion/react-native-engine-sdk`

#### Step 4: Initialize the SDK

In your app, you’ll need to initialize the SDK by rendering its React component. As props, you’ll need to pass your provided bearer token (Step 1 - request a token from your Partner Manager), the Engine endpoint you’re connecting to, and any information you already have about the user if you don’t want the user to enter it again.

There are certain fields that must be provided to the Engine SDK component on initialization:

* API endpoint
* Bearer token
* Required prefilled data (the Engine SDK does not ask for these 3 fields):
  * First name
  * Last name
  * Email address
* Lead IP Address is captured automatically by the SDK from the user's device; you do **not** need to pass this field in your integration.

Here is a sample code snippet of how you might implement the Mobile SDK into your app:

```tsx
import { EngineSdk } from '@moneylion/react-native-engine-sdk';

<EngineSdk
  bearer="{your_token}"
  primaryColor="#11aa34"
  secondaryColor="#444444"
  prefilledData={{
    firstName: 'John',
    lastName: 'Doe',
    email: 'johndoe@gmail.com',
    // ...any other data you don't want to ask the user for again
  }}
  clientTags={{ /* ...optional Client Tags, see the Client Tags section below */ }}
  {/* ...optional event handlers, see the Event Handlers section below */}
/>
```

## User Experience & Demo

*Note: any screen where that info is prefilled will be skipped.*

![Engine Mobile SDK Screen 2: Loan Amount](/files/S6Zc2a3rX4V6inKFJdQB)

![Engine Mobile SDK Screen 3: Loan Purpose](/files/Czr29txIIrWrzVkcF70I) ![Engine Mobile SDK Screen 4: Date of Birth](/files/fLO7FiMw087eXvlr5FOl)

![Engine Mobile SDK Screen 5: Address](/files/YvyKTW5Z6MHbK6ovtRNi) ![Engine Mobile SDK Screen 6: Phone Number](/files/zK0avhWeyhLVcDrFpLVJ)

![Engine Mobile SDK Screen 7: Credit Rating](/files/nQGm08kNDM0I67xF2FuG) ![Engine Mobile SDK Screen 8: Property Status](/files/RokqgqVW80tEHwIdYxoz)

![Engine Mobile SDK Screen 9: Highest Level of Education](/files/ahUAGYgILPUqHxGvACfq) ![Engine Mobile SDK Screen 10: Employment Status](/files/m9BoP0MAmhGaPB3EBXyB)

![Engine Mobile SDK Screen 11: Annual Income](/files/Gg21Kn6Dt8tPGK4CgmbH) ![Engine Mobile SDK Screen 13: SSN](/files/xusuFWpqVJhG62UKXZpu)

![Engine Mobile SDK Screen 13: Confirm Details](/files/bP46ZxshFLjqswUWree0) ![Engine Mobile SDK Screen 14: Offer Wall](/files/jTasdYSC3Ny7sgjPe0nd)

## Adding Client Tags for Reporting

The Engine Mobile SDK supports adding your unique Client Tags to track performance of specific campaigns or users. All Client Tag data is attributed to the lead level.

Client Tags can be added by including a `clientTags` attribute at the top level of the request body, where the value is an array of strings. We strongly recommend only including one element per array for ease of reporting/attribution - if you want two separate tags, include the second tag under a different key. There is no limit to the number of keys in the clientTags object.

### Example

Here is a sample code snippet of how you might implement the Mobile SDK into your app, with sample `clientTags` appended:

```jsx
<EngineSdk
    bearer="{your_token}"
    primaryColor=”#11aa34”
    secondaryColor=”#444444”
    prefilledData={ any data you don't want to ask for again }
    "clientTags": {
        "clientId": ["client1"],
        "trafficsource": ["email"],
        "campaignId": ["campaign1"]
    }
    { ...optional Event Handlers, please see Event Handler section }
/>
```

See our [GET Lead Client Tags](https://engine.tech/docs/api-reference/#get-lead-client-tags) endpoint for information on attributing lead analytics to your own client tags, if you decide to hit our Analytics API for reporting.

### Supported Client Tag Keys

If you plan for the Engine team to set up reporting (i.e. you do not plan to hit our Analytics API), these are the only keys that are currently fully supported. If a different key is needed, please reach out to your partner manager - we *may* be able to accommodate, but adding nonstandard keys will increase the time it takes Engine to report Client Tag values back to you, and is therefore not recommended.

* agentId
* campaignId
* clientId
* deviceid
* medium
* sourceId
* subid
* subid1
* subid2
* subid3
* target
* trafficsource
* userid

## Prefilling Lead Data

The `prefilledData` object should be structured with any fields you want the user not to have to answer themselves, if you already have that info available. Here is an example of `prefilledData` as a Javascript Object. You may prefill as many of these fields as desired; the only 3 *required* are `firstName`, `lastName`, and `email`.

```jsx
{
    "firstName": "Bob", // required
    "lastName": "Dylan", // required
    "email": "bobdylan@gmail.com", // required
    "loanAmount": 7500, 
          // you can instead use "defaultSliderLoanAmount", if you want to suggest a default loan amount but still allow the user to edit it.
          // if "loanAmount" is prefilled, "defaultSliderLoanAmount" will be ignored
    "purpose": "credit_card_refi",
    "dateOfBirth": new Date("1990-01-01"),
    "address": {
      "street": "1600 Pennsylvania Avenue",
      "city": "Washington",
      "state": "DC",
      "zip": "20500"
    },
    "primaryPhone": "2325624852",
    "creditRating": "good",
    "propertyStatus": "own_with_mortgage",
    "educationLevel": "associate",
    "employmentStatus": "employed",
    "annualIncome": "20000",
    "hasDirectDeposit": false,
    "ssn": "512-54-7862",
    "bankRoutingNumber": "123456789",
    "bankAccountNumber": "55555555555"
}
```

{% hint style="info" %}
**Prefilled values cannot be edited by the user** on the final confirm-details screen (before submitting the form). If the user's info is incorrect (e.g. Address is incorrect outdated), the user should edit the info in your app directly.
{% endhint %}

### Example React Native Implementation

Here is a sample code snippet of how you might implement the Mobile SDK into your app, with all `prefilledData` fields populated:

<pre class="language-jsx"><code class="lang-jsx">&#x3C;EngineSdk
<strong>    bearer="{your_token}"
</strong>    primaryColor=”#11aa34”
    secondaryColor=”#444444”
    prefilledData={
              "firstName": "Bob", // required
              "lastName": "Dylan", // required
              "email": "bobdylan@gmail.com", // required
              "loanAmount": 7500, 
                    // you can instead use "defaultSliderLoanAmount", if you want to suggest a default loan amount but still allow the user to edit it.
                    // if "loanAmount" is prefilled, "defaultSliderLoanAmount" will be ignored
              "purpose": "credit_card_refi",
              "dateOfBirth": new Date("1990-01-01"),
              "address": {
                "street": "1600 Pennsylvania Avenue",
                "city": "Washington",
                "state": "DC",
                "zip": "20500"
              },
              "primaryPhone": "2325624852",
              "creditRating": "good",
              "propertyStatus": "own_with_mortgage",
              "educationLevel": "associate",
              "employmentStatus": "employed",
              "annualIncome": "20000",
              "hasDirectDeposit": false,
              "ssn": "512-54-7862",
              "bankRoutingNumber": "123456789",
              "bankAccountNumber": "55555555555"
            }
    clientTags={ ...optional Client Tags, please see following Client Tag section }
    {...optional Event Handlers, please see Event Handler section}
    />
</code></pre>

{% hint style="info" %}
For any of these fields, *DO NOT* prefill any fields that are empty or otherwise unavailable. If you do not have info for a particular field available to prefill, omit that field in your `prefilledData`object.
{% endhint %}

For example, this is **incorrect**:

```
{..."firstName": "Bob", "lastName": "Dylan", "address": {}, ...otherAttributes}  
// empty address is prefilled, which prevents the user from submitting complete info, and they will not receive offers
```

This is **correct**:

```
{"firstName": "Bob", "lastName": "Dylan", ...otherAttributes} 
//address is not prefilled, which forces the user to input their address in the UI and will allow them to submit complete info and be considered for offers
```

### PrefilledData Type Validations

The data should follow a type schema, described here in Typescript notation:

```typescript
interface PrefilledLeadData {
    firstName: string;
    lastName: string;
    email: string;
    loanAmount?: number; // whole numbers only
    defaultSliderLoanAmount?: number | string; // if you want the loan amount slider to default to something other than 5000 but want to allow the user to edit the amount, enter it here
    purpose?: LoanPurpose; // enum below
    dateOfBirth?: Date;
    address?: AddressData; // enum below
    primaryPhone?: string; // leading 1 is fine, but no "+" or "-" chars allowed
    phoneConsent?: boolean;
    ssn?: string; // 9 digits with or without hyphens
    creditRating?: CreditRating; // enum below
    propertyStatus?: PropertyStatus; // enum below
    educationLevel?: EducationLevel; // enum below
    employmentStatus?: EmploymentStatus; // enum below
    annualIncome?: AnnualIncomeBracket; // enum below
    hasDirectDeposit?: boolean;
    bankRoutingNumber?: string; (must be exactly 9 digits)
    bankAccountNumber?: string;
}

interface AddressData {
/** Primary street address */
    street?: string;
    /** (Optional) Apartment Suite */
    apartment?: string;
    city?: string;
    state?: string; // must be a valid 2-letter abbreviation
    zip?: string;
}
```

### Enums/Accepted Values for Certain Fields

Certain fields, although strings, must be within a predetermined list of accepted values. Here are those values:

```tsx
type LoanPurpose = "debt_consolidation" | "credit_card_refi" | 
"home_improvement" | "large_purchases" | "other";

type CreditRating = "excellent" | "good" | "fair" | "poor" | "limited";

type PropertyStatus = "own_with_mortgage" | "rent";

type EducationLevel = "high_school" | "associate" |  "bachelors" |  "masters" |
"other_grad_degree" |  "other";

type EmploymentStatus = "employed" | "self_employed" | "not_employed" | "retired" | 
"military" | "other";

type AnnualIncomeBracket = "20000" | "40000" | "60000" | "100000" | "120000" | "121000";
```

### PrefilledData Sanitization

Prefilled Data must align with the type validations above, and any fields with an accepted value enum must be within that enum. If you (the partner) prefill user data, and any type/value is invalid, that attribute will be stripped, and the lead will have to enter it again manually.

## Event Handlers for Real-Time Tracking

The Engine Mobile SDK has real-time **Event Handlers** so you can track lead progression through the flow in real time (accessing lead data and notifications when leads progress through each step of the SDK, up to and including offer click).

### Implementation

There are 11 Event Handlers you can implement that will be called as a user progresses through the SDK flow.\
Here is a screenshot of a sample code snippet of how you might implement the Mobile SDK into your app, with all 11 implemented (you can choose any or all of these to implement):

<figure><img src="/files/6fLCoD17ROmkOYinzeFx" alt=""><figcaption></figcaption></figure>

For your convenience, the same code snippet is pasted below as text, so you can copy-paste the bulk of the code. Please wrap the following snippet in a self closing \`<\` and \`/>\`, following the screenshot above (the text below omits the wrapping \`\</>\` so as not to trigger a CSS attack warning on this page)

```
EngineSdk
    bearer="{your_token}"
    primaryColor=”#11aa34”
    secondaryColor=”#444444”
    prefilledData={ any data you don't want to ask for again }
    clientTags={ ...optional Client Tags, please see previous Client Tag section }

    onInitialize={(storedLeadUuid, storedLeadData) => { your handler logic }}

    onCreate={(leadUuid, leadData, page, editingPage) => { your handler logic }}

    onUpdate={(leadUuid, leadData, page, editingPage) => { your handler logic }}

    onNavigate={(leadUuid, leadData, currentPage, nextPage, editingPage) => { your handler logic }}

    onSubmit={(leadUuid, leadData) => { your handler logic }}

    onRateTableRender={({ timestamp, rateTableUuid, loanOffers, specialOffers }) => { your handler logic }}

    onOfferClick={({
        timestamp,
        leadUuid,
        offerUuid,
        financialInstitutionName,
        financialInstitutionUuid,
        productType,
        productSubType,
        loanAmount,
        apr,
        termLength,
        monthlyPayment
      }) => { your handler logic }}

    onErrorPageView={({ timestamp, leadUuid, leadData, toPage, editingPage? })} =? { your handler logic }

    onErrorPageRetryProps={({ timestamp, leadUuid, leadData, toPage, editingPage? })} =? { your handler logic }

    onExit={() => { your handler logic }}
    
    onError={({ code, severity, message, timestamp, sdkVersion, error, statusCode, additionalInfo }) => { your handler logic in the case of errors }}
```

`{ your handler logic }` is a placeholder for any function you actually want to execute when that handler is called. Channel Partners typically use Event Handlers to call their own endpoints when leads hit specific funnel stages, for tracking, retargeting, or other purposes.

{% hint style="info" %}
Each handler takes a structure object as an argument. Feel free to choose whichever arguments are useful to include in your handler logic, and omit the rest.
{% endhint %}

### Descriptions

Below are descriptions of the various event handlers and the data each one has access to when it fires. All event handlers have access to `timestamp`, which is a string representation of the moment (in UTC) the handler was called.

1. `onInitialize` is called when the SDK is initialized, after any data saved to local storage has been loaded. The saved data is supplied to the handler. `onInitialize` has access to the savedLeadUuid and savedLeadData, if the user's data was stored on the device from a previous session.<br>

   ```
   interface onInitializeProps {
   	timestamp: string;
   	storedLeadUuid: string | null;
   	storedLeadData: PrefilledLeadData | null;
   }

   { /* storedLeadUuid and storedLeadData will be retried from device storage 
   if the user previouly went through the app and the SDK stored those items to 
   local storage */ }
   ```

   \
   All page names provided to the handlers, such as `page`, `nextPage`, and `editingPage`, will be in snake\_case format.<br>

   <div data-gb-custom-block data-tag="hint" data-style="info" class="hint hint-info"><p>*Note: PrefilledLeadData here here and for the rest of this page indicates the data type, i.e. what fields/data formats might be present. This will include all fields set on the lead at that point, regardless of whether the data was initially prefilled, or later submitted by the user.</p></div>

   <br>
2. `onCreate` is called when the SDK first creates a lead with the Engine service. The data used to create the lead is supplied along with the current page and the page the user is editing, if any.<br>

   ```
   interface onCreateProps {
   	timestamp: string;
   	leadUuid: string | null;
   	leadData: PrefilledLeadData;
   	createPage: PageName;
   	editingPage?: EditablePageName | null;
   }

   { /* leadUuid may be null in rare cases where the POST request to the Engine API 
   fails - if it fails, the SDK will try again on the next step of the user flow */ }

   { /* editingPage is present/available if the lead most recently came from an 
   "Edit Selected Field" page */ }
   ```

   <br>
3. `onUpdate` is called whenever the SDK updates a lead with the Engine service. It receives the same data that the onCreate handler receives.<br>

   ```
   interface onUpdateProps {
   	timestamp: string;
   	leadUuid: string;
   	leadData: PrefilledLeadData;
   	updatePage: PageName;
   	editingPage?: EditablePageName | null;
   }
   { /* editingPage is present/available if the lead most recently 
   came from an "Edit Selected Field" page */ }
   ```

   <br>
4. `onNavigate` is called whenever the user navigates to a new page. It receives the current lead data, the current page, the next page, and the page the user is editing if any.<br>

   ```
   interface onNavigateProps {
   	timestamp: string;
   	leadUuid: string | null;
   	leadData: PrefilledLeadData;
   	fromPage: PageName;
   	toPage: PageName;
   	editingPage?: EditablePageName | null;
   }

   { /* leadUuid may be null in rare cases where the POST request to the Engine API 
   fails - if it fails, the SDK will try again on the next step of the user flow */ }

   { /* editingPage is present/available if the lead most recently came from an 
   "Edit Selected Field" page */ }
   ```

   <br>
5. `onSubmit` is called when the user submits their information to the Engine service and receives offers. The submitted data is supplied to the handler.<br>

   ```
   export interface onSubmitProps {
   	timestamp: string;
   	leadUuid: string | null;
   	leadData: PrefilledLeadData;
   }
   ```

   s
6. `onRateTableRender` is called when the user submits their information to the Engine service and receives offers. The submitted data is supplied to the handler.<br>

   ```
   interface onRateTableRenderProps {
   	timestamp: string;
   	rateTableUuid: string;
   	loanOffers: object[];
   	specialOffers: object[];
   }
   ```
7. `onOfferClick` is called when the user submits their information to the Engine service and receives offers. The submitted data is supplied to the handler.<br>

   ```
   interface onOfferClickProps {
   	timestamp: string;
   	leadUuid: string;
   	offerUuid: string;
   	financialInstitutionName: string;
   	financialInstitutionUuid: string;
   	productType: string;
   	productSubType: string;
   	loanAmount: number | string;
   	apr: number | string;
   	termLength: number | string;
   	monthlyPayment: number | string;
   }

   { /* loanAmount, apr, termLength, and monthlyPayment only exist on loanOffers. 
   If the offer clicked comes from the specialOffers array, the string value for 
   each of the 4 parameters will be "undefined for specialOffers". */ }
   ```
8. `onErrorPageView` is called when the user is driven to the Error page ("Something Went Wrong"). This alerts you to when there may be an issue with Engine's servers, and when your users are not able to complete their application and submit for offers successfully.<br>

   ```
   interface onErrorPageViewProps {
   	timestamp: string;
   	leadUuid: string | null;
   	leadData: PrefilledLeadData;
   	fromPage: PageName;
   	editingPage?: EditablePageName | null;
   }
   ```
9. `onErrorPageRetry` is called when the user taps "Retry" on the error page. This informs you when the user attempts to retry on the previous page.<br>

   ```
   interface onErrorPageRetryProps {
   	timestamp: string;
   	leadUuid: string | null;
   	leadData: PrefilledLeadData;
   	toPage: PageName;
   	editingPage?: EditablePageName | null;
   }
   ```
10. `onExit` is called when the user attempts to exit the SDK flow. It is important to supply this handler in order to understand when the user exited the SDK and retarget the user to complete the flow.<br>

    ```
    interface onExitProps { timestamp: string }
    ```
11. `OnError` is called when an error occurs during SDK operations. You may use this to log errors to your error tracking service or implement custom error handling logic.<br>

    ```
    interface EngineSdkError { 
      code: ErrorCodes; 
      severity: "warning" | "error"; 
      message: string; 
      timestamp: string; 
      sdkVersion: string; 
      error?: Error | unknown; 
      statusCode?: number; 
      additionalInfo?: Record<string, unknown>; 
    } 
    enum ErrorCodes { 
      // Configuration 
      MISSING_CONFIG = "MISSING_CONFIG", 

      // Storage 
      DATA_READ_ERROR = "DATA_READ_ERROR", 
      DATA_WRITE_ERROR = "DATA_WRITE_ERROR", 
      
      // Flow 
      FLOW_ERROR = "FLOW_ERROR", 
      
      // UI 
      UI_CRASH = "UI_CRASH", 
      
      // Network 
      NETWORK_REQUEST_ERROR = "NETWORK_REQUEST_ERROR", 
      NETWORK_SERVER_ERROR = "NETWORK_SERVER_ERROR", 
      NETWORK_OTHER_ERROR = "NETWORK_OTHER_ERROR", 
      NETWORK_TIMEOUT = "NETWORK_TIMEOUT", 
    }
    ```

    \
    `additionalInfo` contains additional information that is relevant to the error that occured. Some examples of what information it could contain:\
    • `leadData` (Record\<string, unknown>) - The lead data object at the time of error\
    • `leadUuid` (string) - The lead UUID

    • `page` (string) - The current page where the error occurred

    • `productTypes` (string) - Product types being processed

    • `clientTags` (Record\<string, string\[]> - Client tags

    • `rateTableUuid` (string) - Rate table UUID

{% hint style="info" %}
All page names provided to the handlers, such as `page`, `nextPage`, and `editingPage`, will be in snake\_case format.
{% endhint %}

### Example Event Handler Calls

#### Scenario 1: No user info is prefilled

**1. `onInitialize`**

When the user first enters the SDK and reaches the splash page:

`onInitialize` fires with:

`onInitialize({ timestamp, storedLeadUuid,storedLeadData })`

{% hint style="info" %}
\*Note: unless a lead has already begun, exited, and re-entered the SDK, `storedLeadUuid` and `storedLeadData` will be `null`. If the user re-enters the SDK after a previous session where a leadUuid was created and leadData was stored in local storage on their device, the handler will fire with those values.
{% endhint %}

**2. `onNavigate` and `onCreate`**

When the user navigates to the following screen (which differs depending how much user info is prefilled), `onNavigate` and `onCreate` will fire. For example, if no info is prefilled, the user will navigate away from the splash screen, where `onNavigate` fires:

`onNavigate({ timestamp, leadUuid, leadData, currentPage, nextPage })`

Values will be as follows:

* **leadUuid**: null (unless the user returned from a previous session, where the `leadUuid` was stored in local storage)
* **leadData**: any user info prefilled, e.g. `{firstName: "Bob", lastName: "Dylan", email: "bobdylan@tambourineman.com", {...other prefilled data, if provided}}`
* **currentPage**: `splash`
* **nextPage**: `loan_amount`

`onCreate({ timestamp, leadUuid, leadData, createPage })`

Values will be as follows:

* **leadUuid**: `{UUID string in UUID Format}` ([Info on UUID formats](https://www.uuidtools.com/what-is-uuid))
* **leadData**: `{firstName: "Bob", lastName: "Dylan", email: "bobdylan@tambourineman.com", ipAddress: "x.x.x.x"}`
* **createPage**: `splash`

The user will then navigate into the `loan_amount` screen (again, assuming `loanAmount` is not prefilled):

<div align="left"><figure><img src="/files/S6Zc2a3rX4V6inKFJdQB" alt="" width="188"><figcaption></figcaption></figure></div>

When the user presses `Continue`, `onNavigate` will fire:

`onNavigate({ timestamp, leadUuid, leadData, currentPage, nextPage })`

Values will be as follows:

* **leadUuid**: {the lead's UUID as a string}
* **leadData**: `{"firstName":"Bob","lastName":"Dylan","email":"bobdylan@tambourineman.com","ipAddress":"x.x.x.x"}`
* **currentPage**: `loan_amount`
* **nextPage**: `loan_purpose`

The lead is also updated at this point, so `onUpdate` fires:

`onUpdate({ timestamp, leadUuid, leadData, page })`

Values will be as follows:

* **leadUuid**: {the lead's UUID as a string}
* **leadData**: `{"firstName":"Bob","lastName":"Dylan","email":"bobdylan@tambourineman.com","ipAddress":"x.x.x.x","loanAmount":5000}`
* **page**: `loan_amount`

**3. Subsequent `onNavigate` and `onUpdate` calls**

`onNavigate` and `onUpdate` will fire at each user navigation, with the `leadData` object growing as fields are updated onto the lead. `currentPage` / `nextPage` values are as follows:

* `loan_purpose`
* `date_of_birth`
* `address`
* `phone`
* `credit_rating`
* `property_status`
* `education_level`
* `employment_status`
* `annual_income`
* `ssn`
* `confirm_details`
* `offers`

**4. `onSubmit`**

When the user presses `See my offers` on the confirm\_details screen:

<div align="left"><figure><img src="/files/bP46ZxshFLjqswUWree0" alt="" width="188"><figcaption></figcaption></figure></div>

The Event Handlers that fire are:

`onSubmit({ timestamp, leadUuid, leadData })`

`onNavigate({ timestamp, leadUuid, leadData, currentPage, nextPage })`

`onUpdate({ timestamp, leadUuid, leadData, page })`

* `currentPage` and `page` here will = `confirm_details`
* `nextPage` will = `offers`

**5. `onRateTableRender`**

When the user lands on the offer page, `onRateTableRender` fires with a `rateTableUuid`, a `leadUuid`, and two arrays: `loanOffers` and `specialOffers`

**rateTableUuid**

This is associated with that application from that lead. Useful to identify and map with the `leadUuid`.

**loanOffers**

This is an array of objects. Each object will have the following attributes:

* offerUuid
* financialInstitutionName
* financialInstitutionUuid
* productType
* productSubType
* loanAmount
* apr
* termLength
* monthlyPayment

**specialOffers**

This is an array of objects. Each object has the following attributes:

* offerUuid
* name
* financialInstitutionName
* financialInstitutionUuid
* productSubType

6. `onOfferClick`

onOfferClick fires when a lead clicks on an offer from the offer table. The `financialInstitutionName` , `productType`, and `productSubType` will indicate whether the user clicked a loan offer or special offer.

#### Scenario 2: Some (but not all) user info is prefilled

When some user info is prefilled, the first `onNavigate` fire away from the splash screen will have:

* `currentPage=splash`
* `nextPage={the first page in the flow that was not prefilled}`

The rest of the flow will be the same as Scenario 1 above.

#### Scenario 3: All user info is prefilled

When some user info is prefilled, the first `onNavigate` fire on the first navigation away from the splash screen will have:

* `currentPage=splash`
* `nextPage=confirm_details`

`onCreate` will also fire at this point, since this is when the `leadUuid` is created by posting the lead to the Engine API.

The user will submit by pressing `See my offers`, and `onSubmit`, `onNavigate`, and `onUpdate` will function the same as in Scenario 1.

{% hint style="info" %}
Note: `onUpdate` will still fire when the user presses `See my offers` on the confirm\_details screen because this is when legal consents are accepted / set to the lead object.
{% endhint %}

#### `onExit` - wherever a user exits the flow

`onExit` will fire whenever a user leaves the Engine Mobile SDK, regardless of where in the flow the user left the flow. You can use this to infer whether a lead completed / submitted the loan form, depending what other Event Handler callbacks have fired (assuming you set those up as well). More details in the next section:

#### `onError` - when an error occur

For example, if the SDK API call to synchronize a lead fails, the `onError` callback is invoked with these props

```
{ 
  “code”: “NETWORK_REQUEST_ERROR”, 
  “severity”: “error”, 
  “message”: “Failed to synchronize lead data”, 
  “timestamp”: “Mon, 10 Nov 2025 05:46:47 GMT, 
  “sdkVersion”: “1.5.7”, 
  “error”: Error, 
  “statusCode”: 401, 
  “additionalInfo”: { 
    “leadData”: { 
      “firstName”: “Bob”, 
      “lastName”: “Smith”, 
      “email”: “test@email.com”, 
    }   
  }
}
```

### How to use Event Handlers for Tracking

Event handlers can be used to track how far a lead progressed in the flow, i.e. if your Event Handler callbacks fire an `onExit` but do not fire an `onSubmit`, and/or if your `onNavigate` handlers never fire with `nextPage=offers`, you can infer that the lead exited the flow before submitting to see offers. This allows you to retarget the lead accordingly to entice them to complete the form and see their offers.

Similarly, if your Event Handler fires indicate the lead submitted the form (i.e. `onSubmit` fires and/or `onNavigate` fires with `nextPage=offers`, but you do not receive reporting/notifications that the lead converted on a loan or other product, you may retarget the lead to entice them to re-enter the SDK, immediately see their offers (since on re-entry the user will be directed straight to the `offers` screen, and ideally convert on an available offer. See [Reporting Options for Channel Partners](https://engine.tech/developer-center/loans-savings-second-look-marketplace/reporting-options-for-channel-partners) for your available options for receiving conversion reporting from Engine by MoneyLion.

## User Flow

When the `EngineSdk` component is rendered, it will first determine how much information it needs to ask from the user (if not provided in `prefilledData`). It will then present the user with a splash screen explaining the process to them. When they continue, if all info is prefilled, they are taken directly to the summary screen with their info and can immediately submit to see their offers. Otherwise, they navigate through subsequent screens to collect any missing information, before being presented with the summary screen. From the summary screen, the user is able to correct any info\* before submitting it and receiving their offers.

{% hint style="info" %}
*\*Note: only info submitted directly by the user can be edited; prefilledData cannot be edited at this time. If firstName/lastName/email or other \`prefilledData\` fields need to be updated, that should be done in the host app, not the SDK data capture.*
{% endhint %}

At each step through the loan form, the information is saved to the phone and an attempt is made to submit it to the Engine API (updating lead info to track progress/dropoff). If no network is available, the SDK will proceed to the next step without submitting to the Engine API. A network is only required for the final step of submitting the form and receiving offers.

### Dropoff and Reentry

Any time a user exits and re-enters the Engine Mobile SDK, they will restart at the same point they were before they exited the SDK.

* If a user was in the middle of the form but did not submit, they will start back on that step (and all previously entered info will be retrieved from local storage so they will not need to enter it again).
* If a user submitted a form and saw offers before exiting, the next time they re-enter the SDK (within the offer expiration period of 31 days), they will be directed back to their offer screen. If offers have expired (>31 days later), they will start at the beginning of the form (save for any prefilled info that will be skipped).

### Offer Expiration

Offers will expire 31 days after initially received by the user. Any time within the valid period, users re-entering the SDK will see the same available offers (consumers are unlikely to have material changes to their credit worthiness within that period, which is why offers are cached for that time duration). Users entering the SDK >31 days after they initially received those offers will start the form again as a new lead (minus any prefilled info).

### Customization

You can also customize some of the branding by changing the primary and secondary colors to match your own brand. Use the `primaryColor` and `secondaryColor` attributes to accomplish this.

{% hint style="info" %}
\*Dark mode is not currently available in the Engine Mobile SDK. This will be coming in a future update.
{% endhint %}

## Changelog

<details>

<summary>View the full React Native Personal Loan SDK changelog</summary>

### 2.1.0 (2026-07-21)

* Upgraded build tooling to support latest React Native features and improvements
* Added loading state to button group component for better user feedback during async operations

### 2.0.0 (2026-07-07)

* Replaced deprecated React Native `SafeAreaView` usage with `react-native-safe-area-context` to align with the current React Native recommendations and improve compatibility

### 1.10.0 (2026-06-23)

* Fixed modal animation and safe area handling for improved UI behavior
* Added `react-native-safe-area-context` as a required peer dependency

### 1.9.0 (2026-04-21)

* Improved type definitions

### 1.8.0 (2026-04-14)

* Improved SSN handling by masking sensitive values for enhanced user privacy and security

### 1.7.1 (31 Mar 2026)

* Renamed `consentsToSms` field to `phoneConsent` in lead creation data

### 1.7.0 (25 Mar 2026)

* Fixed a potential infinite loop in masked input fields (address, date of birth, phone, SSN)
  * Inputs no longer re-trigger updates when value is set externally
* Improved prop handling in form input components to prevent unnecessary re-renders
* Fixed type alignment across lead data utilities
* Moved `react-native-svg` to peer dependencies (required version: `>=15.11.0 <16`)

</details>


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://even-financial.gitbook.io/developer-center/marketplace-integrations/mobile-sdks/personal-loans/react-native.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
