React Native SoluCX Widget

React Native package for displaying SoluCX satisfaction surveys (NPS, CSAT, CES) inside your app.

Overview

The widget loads the survey via WebView and automatically manages display options (type, height, frequency, sampling, and suppression rules). You only need to provide customer/transaction data and the SDK handles the rest.

Display modes: Bottom, Top, Modal, and Inline.

Installation

npm
pnpm
bun
yarn
npx expo install @solucx/react-native-solucx-widget

Quick Start

There are two ways to integrate the widget. Choose the one that best fits your project:


This is the most flexible approach. You mount a SoluCXWidgetHost once at the app root and trigger the widget from anywhere using SoluCXWidget.create(...).show().

Step 1: Mount SoluCXWidgetHost at the root

SoluCXWidgetHost is the container that renders the widget. It must be mounted once, in the root component.

// App.tsx
import { SoluCXWidgetHost } from '@solucx/react-native-solucx-widget';

export default function App() {
  return (
    <>
      <NavigationContainer>
        <Stack.Navigator>{/* your screens */}</Stack.Navigator>
      </NavigationContainer>

      <SoluCXWidgetHost />
    </>
  );
}

Step 2: Trigger the widget from any screen

Call SoluCXWidget.create() whenever you want to display the survey. Just provide the key, display type, and customer data — the SDK fetches the remaining settings (height, frequency, sampling, etc.) automatically from the journey panel.

// CheckoutScreen.tsx
import { SoluCXWidget } from '@solucx/react-native-solucx-widget';

function onCheckoutComplete() {
  SoluCXWidget
    .create('YOUR_SOLUCX_KEY')
    .setType('modal')
    .setData({
      journey: 'post_sale',
      customer_id: 'customer_123',
      email: '[email protected]',
      name: 'John Smith',
    })
    .show();
}

That's it. The SDK calls the API, applies the display rules configured in the panel, and decides whether the widget should appear or not.

Step 3 (optional): Override options locally

If you need to override the panel settings for a specific case, use .setOptions(). When called, the widget ignores the remote configuration and uses only the provided values.

SoluCXWidget
  .create('YOUR_SOLUCX_KEY')
  .setType('modal')
  .setData({ journey: 'quarterly_nps', customer_id: 'customer_789' })
  .setOptions({
    height: 600,
    samplingPercentage: 50,
    maxAttemptsAfterDismiss: 5,
    waitDaysAfterWidgetDismiss: 90,
  })
  .show();

Component Integration (alternative)

If you prefer to control display through JSX itself, use SoluCXWidgetView directly. In this mode, you don't need to mount SoluCXWidgetHost — the component is self-contained.

// CheckoutScreen.tsx
import { SoluCXWidgetView } from '@solucx/react-native-solucx-widget';

export function CheckoutScreen() {
  return (
    <SoluCXWidgetView
      soluCXKey="YOUR_SOLUCX_KEY"
      type="inline"
      data={{
        journey: 'post_sale',
        customer_id: 'customer_123',
        email: '[email protected]',
        name: 'John Smith',
      }}
      callbacks={{
        onOpened: (userId) => {
          console.log('Widget displayed for:', userId);
        },
      }}
    />
  );
}

The component is rendered at the exact position where it is declared in JSX. Ideal for inline mode.

Playground

Display Modes

ModeBehavior
bottomFixed at the bottom of the screen (default)
topFixed at the top of the screen
modalCentered overlay that blocks interaction with the background
inlineInserted into the layout flow, at the position where the component is declared
// Bottom: fixed bar at the bottom (default)
SoluCXWidget.create('KEY').setType('bottom').setData(data).show();

// Top: fixed bar at the top
SoluCXWidget.create('KEY').setType('top').setData(data).show();

// Modal: centered overlay that blocks the background
SoluCXWidget.create('KEY').setType('modal').setData(data).show();

// Inline: integrated into the layout flow (respects JSX position)
SoluCXWidget.create('KEY').setType('inline').setData(data).show();

Complete Example with Callbacks

This example shows every stage of the widget lifecycle — from preparation to closure:

import { SoluCXWidget } from '@solucx/react-native-solucx-widget';

function showSurvey() {
  SoluCXWidget
    .create('YOUR_SOLUCX_KEY')
    .setType('modal')
    .setData({
      journey: 'support',
      customer_id: 'customer_456',
      email: '[email protected]',
      name: 'Maria Santos',
      employee_id: 'agent_01',
      employee_name: 'Carlos',
    })
    .setCallbacks({
      onPreOpen: (userId) => {
        console.log('Preparing widget for:', userId);
      },
      onOpened: (userId) => {
        console.log('Widget displayed for:', userId);
        analytics.track('survey_shown', { userId });
      },
      onBlocked: (reason) => {
        // Display rules prevented the widget from showing
        console.log('Widget blocked:', reason);
        // Ex: "BLOCKED_BY_WIDGET_DISMISS_INTERVAL", "BLOCKED_BY_SAMPLING"
      },
      onClosed: () => {
        console.log('User closed the widget');
      },
      onCompleted: (userId) => {
        console.log('Survey completed by:', userId);
        analytics.track('survey_completed', { userId });
      },
      onPartialCompleted: (userId) => {
        console.log('Survey partially completed by:', userId);
      },
      onError: (message) => {
        console.error('Widget error:', message);
      },
    })
    .show();
}

Dismiss the widget programmatically

import { SoluCXWidget } from '@solucx/react-native-solucx-widget';

SoluCXWidget.dismiss();

API

SoluCXWidget (main class)

Builder methods (builder pattern)

MethodDescription
SoluCXWidget.create(soluCXKey)Creates a new widget instance
.setType(type)Sets the type: 'bottom', 'top', 'modal', 'inline'
.setData(data)Sets user/transaction data
.setOptions(options)Sets local widget options (optional — if not called, fetches from API)
.setCallbacks(callbacks)Sets event callbacks
.show()Displays the widget

Static methods

MethodDescription
SoluCXWidget.dismiss()Closes the widget programmatically

Instance methods (log management)

Use builder data (instanceKey, journey, userId) to access logs for the correct combination.

MethodDescription
.getWidgetLogs()Returns widget logs
.overrideTimestamp(field, date)Overrides an event timestamp (debug/testing)
.clearWidgetLogs()Clears all logs

WidgetData

Data sent to identify the customer and the survey context.

interface WidgetData {
  // Journey identifier (required in practice)
  journey?: string;

  // User identification
  customer_id?: string;
  name?: string;
  email?: string;
  phone?: string;
  phone2?: string;
  document?: string;
  birth_date?: string;     // Format: YYYY-MM-DD

  // Transaction context
  transaction_id?: string;
  store_id?: string;
  store_name?: string;
  employee_id?: string;
  employee_name?: string;
  amount?: number;
  score?: number;

  // Form ID (Flex Survey only)
  form_id?: string;

  // Custom parameters (use the param_ prefix)
  [key: `param_${string}`]: string | number | undefined;
}

WidgetOptions

Configures widget display options (type, height, frequency, sampling, and suppression rules). Optional — if setOptions() is not called, the widget fetches these settings automatically from the API (journey configuration panel).

interface WidgetOptions {
  enabled?: boolean;
  samplingPercentage?: number;
  type?: string;
  height?: number;
  maxAttemptsAfterDismiss?: number;
  waitDaysAfterWidgetDisplayAttempt?: number;
  waitDaysAfterWidgetFirstAccess?: number;
  waitDaysAfterWidgetDisplay?: number;
  waitDaysAfterWidgetDismiss?: number;
  waitDaysAfterWidgetSubmit?: number;
  waitDaysAfterWidgetPartialSubmit?: number;
}
PropertyTypeDefaultDescription
enabledbooleantrueWhen false, blocks immediately without validating other rules
samplingPercentagenumber100Percentage of users who will see the widget (0-100). 100 = everyone sees it. 0 = nobody sees it
typestring'bottom'Display type: 'bottom', 'top', 'modal', 'inline'. Can be configured remotely via the journey panel
heightnumber600Fixed widget height in pixels. 0 = automatic. Can be configured remotely via the journey panel
maxAttemptsAfterDismissnumber0Maximum number of displays per experience. 0 = no limit. Resets when the experience ID changes
waitDaysAfterWidgetDisplayAttemptnumber0Days to wait after a blocked display attempt
waitDaysAfterWidgetFirstAccessnumber0Days to wait after the user's first access to the journey
waitDaysAfterWidgetDisplaynumber0Days to wait after any widget display
waitDaysAfterWidgetDismissnumber0Days to wait after the user closes the widget
waitDaysAfterWidgetSubmitnumber0Days to wait after full survey submission
waitDaysAfterWidgetPartialSubmitnumber0Days to wait after partial survey submission

WidgetCallbacks

Functions called at each stage of the widget lifecycle. All are optional.

interface WidgetCallbacks {
  onPreOpen?: (userId: string) => void;
  onOpened?: (userId: string) => void;
  onBlocked?: (blockReason: BlockReason | string | undefined) => void;
  onClosed?: () => void;
  onCompleted?: (userId: string) => void;
  onPartialCompleted?: (userId: string) => void;
  onError?: (message: string) => void;
  onPageChanged?: (page: string) => void;
  onQuestionAnswered?: () => void;
  onResize?: (height: string) => void;
}

Lifecycle

When .show() is called, the SDK executes the following steps:

  1. Checks local options (enabled, sampling, attempts, intervals)
  2. If blocked: calls onBlocked with the reason and the widget does not appear
  3. If allowed: requests the survey URL from the SoluCX API (preflight)
  4. Calls onPreOpen → displays the widget → calls onOpened
  5. During the survey, the user interacts with the form
  6. When the user answers the main rating (NPS/CSAT/CES): calls onPartialCompleted
  7. When the user completes the entire survey: calls onCompleted
  8. When closed (by the user or automatically): calls onClosed
  9. If an error occurs at any step: calls onError

Callback descriptions

CallbackParameterWhen it is called
onPreOpenuserId: stringRight before the widget is displayed, after display rule validation passes
onOpeneduserId: stringWhen the widget is displayed on screen
onBlockedreason: BlockReasonWhen display rules prevent the widget from being displayed. See BlockReason
onClosed-When the widget is closed, either by the user or automatically
onCompleteduserId: stringWhen the user completes the entire survey (all steps)
onPartialCompleteduserId: stringWhen the user answers the main rating (NPS/CSAT/CES)
onErrormessage: stringWhen a survey error occurs (survey expired, already rated, network failure)
onPageChangedpage: stringWhen the user navigates between pages (Flex Survey)
onQuestionAnswered-When the user answers a question (Flex Survey)
onResizeheight: stringWhen the widget height changes

BlockReason

Reasons why display rules prevent the widget from being displayed. These blocks are local (on the device) and do not represent API errors.

type BlockReason =
    | "BLOCKED_BY_DISABLED"
    | "BLOCKED_BY_SAMPLING"
    | "BLOCKED_BY_TRANSACTION_ALREADY_ANSWERED"
    | "BLOCKED_BY_MAX_ATTEMPTS"
    | "BLOCKED_BY_WIDGET_DISPLAY_ATTEMPT_INTERVAL"
    | "BLOCKED_BY_WIDGET_FIRST_ACCESS_INTERVAL"
    | "BLOCKED_BY_WIDGET_DISPLAY_INTERVAL"
    | "BLOCKED_BY_WIDGET_DISMISS_INTERVAL"
    | "BLOCKED_BY_WIDGET_SUBMIT_INTERVAL"
    | "BLOCKED_BY_WIDGET_PARTIAL_SUBMIT_INTERVAL";
ReasonDescription
BLOCKED_BY_DISABLEDWidget is disabled (enabled: false)
BLOCKED_BY_SAMPLINGUser was not selected by sampling (samplingPercentage)
BLOCKED_BY_TRANSACTION_ALREADY_ANSWEREDTransaction was already answered previously
BLOCKED_BY_MAX_ATTEMPTSMaximum number of attempts reached for this experience
BLOCKED_BY_WIDGET_DISPLAY_ATTEMPT_INTERVALWithin the interval after a display attempt
BLOCKED_BY_WIDGET_FIRST_ACCESS_INTERVALWithin the interval after first access to the journey
BLOCKED_BY_WIDGET_DISPLAY_INTERVALWithin the interval after a display
BLOCKED_BY_WIDGET_DISMISS_INTERVALWithin the interval after dismissal
BLOCKED_BY_WIDGET_SUBMIT_INTERVALWithin the interval after full submission
BLOCKED_BY_WIDGET_PARTIAL_SUBMIT_INTERVALWithin the interval after partial submission

Multiple Widgets

Each combination of instanceKey + journey + userId operates with independent logs:

// Widget for "post_sale" journey for user_123
SoluCXWidget.create('KEY').setData({ journey: 'post_sale', customer_id: 'customer_123' }).show();

// Widget for "support" journey for customer_123
SoluCXWidget.create('KEY').setData({ journey: 'support', customer_id: 'customer_123' }).show();

// Each has its own counters, timestamps, and logs

Troubleshooting

Widget does not appear

  • Check if the SoluCX key (soluCXKey) is valid
  • Check internet connectivity
  • Use the onBlocked callback to check if display rules are preventing display
  • Use the onError callback to check for survey errors

Callbacks are not called

  • Confirm the callbacks object is being passed via .setCallbacks() or as a prop on SoluCXWidgetView
  • Check the console for WebView error messages

Broken layout

  • Set a fixed height via setOptions({ height: 600 }) if automatic resizing does not work well in your layout
  • For inline mode, make sure the parent component has enough space

Compatibility

VersionReact NativeExpoiOSAndroid
2.x0.70+50+11+API 21+

License

This package is proprietary to SoluCX. Use is restricted to licensed SoluCX platform customers.