---
title: Stepping through DaVinci flows in React Native
description: Navigate a started DaVinci flow in React Native by handling nodes and collectors
component: orchsdks
page_id: orchsdks:davinci:usage/react-native/05-stepping-through-davinci-flows
canonical_url: https://developer.pingidentity.com/orchsdks/davinci/usage/react-native/05-stepping-through-davinci-flows.html
llms_txt: https://developer.pingidentity.com/orchsdks/llms.txt
docs_for_agents: https://developer.pingidentity.com/build-with-ai/docs-for-agents.md
revdate: Thu, 27 Aug 2026 00:00:00 +0000
keywords: ["DaVinci", "React Native", "Flows", "Nodes", "Collectors", "Navigation"]
section_ids:
  handling_continuenode: Handling ContinueNode
  useDaVinciForm: Normalizing collectors into form fields
  checking_the_composition_of_the_form: Checking the composition of the form
  checking_if_the_required_data_is_complete: Checking if the required data is complete
  explicitCollectors: Handling collectors explicitly by type
  handling_unsupported_fields: Handling unsupported fields
  handling_failurenode_and_errornode: Handling FailureNode and ErrorNode
  handling_successnode: Handling SuccessNode
  handling_specific_collectors: Handling specific collectors
  disposing_an_incomplete_flow: Disposing an incomplete flow
---

# Stepping through DaVinci flows in React Native

[icon: circle-check, set=far]PingOne [icon: react, set=fab]React Native

* [Prepare](01-configuring-the-server.html)

* [Install](02-installing-the-davinci-module.html)

* [Configure](03-configuring-the-davinci-module.html)

* [Start](04-starting-a-davinci-flow.html)

* **Navigate**

* [Manage](06-handling-sessions.html)

Each step of a DaVinci flow returns a node object representing the current state of the flow.

The node has a `type` property that indicates the current state:

**The node types returned during a DaVinci flow**

| Node type      | Description                                                                                                                                                                            |
| -------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `ContinueNode` | Indicates a step in the middle of the flow that requires input.The node contains a list of collectors. Call `next(input)` to submit the collected values and advance to the next step. |
| `SuccessNode`  | Indicates successful authentication.See [Managing sessions and tokens in React Native](06-handling-sessions.html) for post-authentication operations.                                  |
| `FailureNode`  | Represents an unexpected failure, such as a network error, timeout, or server error.Restart the flow when the app receives this node type.                                             |
| `ErrorNode`    | Indicates that the server rejected submitted data, such as an invalid value or a password that does not meet the configured requirements.Correct the data and submit the node again.   |

To complete a DaVinci flow, handle each node type in your React Native application.

The `node` value is `null` until the flow is started, so the example starts the flow when the screen mounts. The empty catch block is safe because `start()` records failures in the hook's `error` state.

Handling different node types in a DaVinci flow

```typescript
import { useEffect } from 'react';
import { useDaVinci } from '@ping-identity/rn-davinci';

function LoginScreen() {
  const { node, next, start, loading, error } = useDaVinci(davinciClient);

  useEffect(() => {
    start().catch(() => {}); // error surfaced via hook state
  }, [start]);

  if (loading) {
    // Show a loading indicator while the native module is processing
  }

  if (node?.type === 'ContinueNode') {
    // Render input fields for node.collectors
    // Call next(input) after collecting values
  }

  if (node?.type === 'SuccessNode') {
    // Authentication succeeded, navigate to the home screen
  }

  if (node?.type === 'FailureNode') {
    // Render a retry button whose onPress handler calls start()
  }

  if (node?.type === 'ErrorNode') {
    // Display node.message or another user-friendly message
  }

  if (error) {
    // Display or log the hook-level error
  }
}
```

## Handling ContinueNode

For `ContinueNode` steps, the server sends a list of collectors that require input or an action from the app.

There are two ways of handling the gathering of the required input:

* [Normalizing collectors into form fields](#useDaVinciForm)

  The `useDaVinciForm` hook maps collectors to a flat list of normalized fields, such as text, password, boolean, single-select, and multi-select fields, so your UI is driven by the kind of input that is needed.

  If the server flow changes from one collector type to another that maps to the same kind, the UI can continue to work without a code change.

  Other benefits:

  * Validation is built in. `form.canSubmit` and `form.issues` tell you whether the step is ready to submit and why not.

  * Default values are pre-seeded. The hook hydrates `form.values` from collector defaults automatically.

  * `form.meta` flags describe mixed steps, including manual input, output-only content, integrations, and unsupported collectors.

  * The submit payload is built for you. `form.input` is ready to pass to `next()`.

  * The code is shorter and does not grow linearly as the number of collector types increases.

* [Handling collectors explicitly by type](#explicitCollectors)

  You work directly with collector objects and their `type` values, such as `TEXT`, `PASSWORD`, and `SINGLE_SELECT`.

  This approach gives you direct control over the values sent to the server. Build a `collectors` array and pass it to `next({ collectors })`.

  Other benefits:

  * There is no normalization layer to learn. The code can use the collector types returned by the flow.

  * You can handle a collector type or integration that your form renderer does not support.

  * You can choose exactly which collector values to submit for the current node.

### Normalizing collectors into form fields

To help you handle the required input, you can use the `useDaVinciForm` hook.

This hook normalizes the collectors into a flat list of typed fields and builds the `next()` input payload for you.

Collecting user input with useDaVinciForm

```typescript
import { useEffect } from 'react';
import { ActivityIndicator, Button, Switch, Text, TextInput, View } from 'react-native';
import { useDaVinci, useDaVinciForm } from '@ping-identity/rn-davinci';

function LoginScreen() {
  const { node, next, start, logoutUser, loading } = useDaVinci(davinciClient);
  const form = useDaVinciForm(node, { next });

  useEffect(() => {
    start().catch(() => {}); // error surfaced via hook state
  }, [start]);

  if (loading) return <ActivityIndicator />;

  if (node?.type === 'ContinueNode') {
    return (
      <View>
        {form.fields
          .filter(field => field.requiresUserInput)
          .map(field => {

            if (field.kind === 'boolean') {
              return (
                <Switch
                  key={field.key}
                  value={Boolean(form.values[field.key] ?? false)}
                  onValueChange={value => form.setValue(field.key, value)}
                />
              );
            }

            if (field.kind === 'singleSelect' && field.options) {
              return (
                <View key={field.key}>
                  {field.options.map(option => (
                    <Button
                      key={option.value}
                      title={option.label}
                      onPress={() => form.setValue(field.key, option.value)}
                    />
                  ))}
                </View>
              );
            }

            return (
              <TextInput
                key={field.key}
                placeholder={field.label}
                secureTextEntry={field.kind === 'password'}
                value={String(form.values[field.key] ?? '')}
                onChangeText={text => form.setValue(field.key, text)}
              />
            );
          })}

        <Button
          title="Continue"
          disabled={!form.canSubmit}
          onPress={() => next(form.input)}
        />
      </View>
    );
  }

  if (node?.type === 'SuccessNode') {
    return (
      <View>
        <Text>Authentication succeeded.</Text>
        <Button title="Sign out" onPress={() => logoutUser()} />
      </View>
    );
  }

  if (node?.type === 'ErrorNode') {
    return (
      <View>
        <Text>{node.message}</Text>
        <Button title="Try again" onPress={() => start()} />
      </View>
    );
  }

  if (node?.type === 'FailureNode') {
    return (
      <View>
        <Text>Something went wrong. Please try again.</Text>
        <Button title="Retry" onPress={() => start()} />
      </View>
    );
  }

  return null;
}
```

The `useDaVinciForm` hook returns `form.fields`, which is a list of normalized collector types, each having the following properties:

**DaVinci normalized collector properties**

| Property            | Type      | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          |
| ------------------- | --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `key`               | `string`  | The collector key used to build the input passed to `next()`.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        |
| `type`              | `string`  | The collector type returned by the server, such as `TEXT` or `PASSWORD`.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             |
| `kind`              | `string`  | The high-level input kind. Examples include `text`, `password`, `boolean`, `singleSelect`, `multiSelect`, `phone`, `device`, `output`, `polling`, `qrCode`, `flow`, and `integration`.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               |
| `executionMode`     | `string`  | How the collector should be handled. One of:- `manual`

  The collector requires a value from the app before the flow can continue.

- `immediate`

  The collector advances the flow immediately, without waiting for other form fields. This includes `SUBMIT_BUTTON`, `ACTION`, `FLOW_BUTTON`, and `FLOW_LINK` collectors.

- `output_only`

  The collector displays information but does not require a value from the user. This includes `LABEL`, `READ_ONLY_TEXT`, `POLLING`, and `QR_CODE` collectors.

- `integration_required`

  The collector requires a separate integration before the flow can continue. Integration packages register the collector types they own when your app initializes them, such as the external IdP and PingOne Protect packages.

- `unsupported`

  The collector type is not handled by the React Native bridge and no initialized integration package has registered it. |
| `requiresUserInput` | `boolean` | Is `true` when the collector needs direct user input.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                |
| `defaultValue`      | varies    | The pre-populated value from the server, if any.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |
| `options`           | varies    | The available choices for a selection collector, if provided.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        |

#### Checking the composition of the form

The `useDaVinciForm` hook provides the `form.meta` property. This provides flags that describe the composition of the current step, which you can use to conditionally render UI:

Using form metadata

```typescript
const form = useDaVinciForm(node);

// Only render a submit button when there are manual fields
if (form.meta.hasManual) {
  // Show input fields and a submit button
}

// Inform the user an integration is needed
if (form.meta.hasIntegrationRequired) {
  // Show an informational message
}
```

**All form.meta flags**

| Flag                     | Description                                                            |
| ------------------------ | ---------------------------------------------------------------------- |
| `hasManual`              | `true` if at least one collector requires direct user input.           |
| `hasOutputOnly`          | `true` if at least one collector is display-only.                      |
| `hasAutoCapable`         | Always `false` for the current React Native DaVinci form helper.       |
| `hasIntegrationRequired` | `true` if at least one collector requires an external integration.     |
| `hasUnsupported`         | `true` if the step contains a collector type the SDK does not support. |

#### Checking if the required data is complete

Use the `form.canSubmit` boolean to check that the required data has been handled appropriately.

If the boolean is `false`, check `form.issues` to identify the reasons.

**Possible reasons preventing form submission**

| Issue code                | When it is raised                                                         |
| ------------------------- | ------------------------------------------------------------------------- |
| `NO_ACTIVE_CONTINUE_NODE` | The current `node` is not an active `ContinueNode`.                       |
| `REQUIRED_VALUE_MISSING`  | A required manual collector does not have a value.                        |
| `INTEGRATION_REQUIRED`    | A collector requires an external integration before submission.           |
| `UNSUPPORTED_COLLECTOR`   | The step contains a collector type that the form helper does not support. |

|   |                                                                                                                                                                                       |
| - | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|   | The `useDaVinciForm` hook is headless. It manages normalized collectors and submit planning, but does not render UI by itself and does not automatically advance ordinary collectors. |

### Handling collectors explicitly by type

If you prefer, handle each collector type explicitly and build the input for `next()` yourself.

Set the values in a `collectors` array and pass it to `next({ collectors })`:

Handling collectors explicitly

```typescript
import { useEffect, useState } from 'react';
import { ActivityIndicator, Button, Text, TextInput, View } from 'react-native';
import { useDaVinci } from '@ping-identity/rn-davinci';

function LoginScreen() {
  const { node, next, start, loading } = useDaVinci(davinciClient);
  const [values, setValues] = useState<Record<string, string>>({});

  useEffect(() => {
    start().catch(() => {}); // error surfaced via hook state
  }, [start]);

  if (loading) return <ActivityIndicator />;

  if (node?.type === 'ContinueNode') {
    const collectors = node.collectors ?? [];

    return (
      <View>
        {collectors.map(collector => {
          if (collector.type === 'TEXT') {
            return (
              <TextInput
                key={collector.key}
                placeholder={collector.label}
                onChangeText={value => {
                  setValues(previous => ({ …​previous, [collector.key]: value }));
                }}
              />
            );
          }

          if (collector.type === 'PASSWORD') {
            return (
              <TextInput
                key={collector.key}
                placeholder={collector.label}
                secureTextEntry
                onChangeText={value => {
                  setValues(previous => ({ …​previous, [collector.key]: value }));
                }}
              />
            );
          }

          return null;
        })}

        <Button
          title="Continue"
          onPress={() =>
            next({
              collectors: collectors.map(collector => ({
                key: collector.key,
                value: values[collector.key] ?? '',
              })),
            })
          }
        />
      </View>
    );
  }

  if (node?.type === 'SuccessNode') {
    return <Text>Authentication succeeded.</Text>;
  }

  if (node?.type === 'ErrorNode') {
    return <Text>{node.message}</Text>;
  }

  if (node?.type === 'FailureNode') {
    return <Button title="Retry" onPress={() => start()} />;
  }

  return null;
}
```

Do not rely on mutating the returned node or collector objects to update the native DaVinci state.

Use the `next()` action with a `collectors` array to submit the values explicitly.

## Handling unsupported fields

If the underlying native SDK cannot create a collector from the server response, the bridge exposes it through `ContinueNode.unsupportedFields`.

Checking for unsupported fields

```typescript
if (node?.type === 'ContinueNode' && node.unsupportedFields?.length) {
  console.warn('Unsupported fields:', node.unsupportedFields);
}
```

|   |                                                                                                                   |
| - | ----------------------------------------------------------------------------------------------------------------- |
|   | Each unsupported field provides a `key` and `type`, allowing your UI to show a placeholder or prevent submission. |

## Handling FailureNode and ErrorNode

The DaVinci module distinguishes between `FailureNode` and `ErrorNode` for different categories of errors encountered during the flow.

* `FailureNode`

  Indicates an unexpected issue that prevents the flow from continuing. This can stem from network problems, timeouts, or server errors.

  You can access the underlying failure details using `node.cause` when it is available.

  Offer the user a way to restart the flow and log the details for support investigation.

* `ErrorNode`

  Indicates that the server rejected submitted data, such as invalid user input or a password that does not meet the configured requirements.

  You can retrieve the specific message using `node.message` and inspect the raw response using `node.input` when it is available.

  Display the error message and offer the user a way to restart the flow. The hook accepts `next()` only for an active `ContinueNode`.

Handling error and failure node types in a DaVinci flow

```typescript
import { useDaVinci } from '@ping-identity/rn-davinci';

function LoginScreen() {
  const { node, start } = useDaVinci(davinciClient);

  if (node?.type === 'FailureNode') {
    console.error('Flow failed:', node.cause);
    return <Button title="Retry" onPress={() => start()} />;
  }

  if (node?.type === 'ErrorNode') {
    console.error('Flow error:', node.message);
    console.error('Server response:', JSON.stringify(node.input));
  }
}
```

For errors that occur while submitting collector values or advancing the flow, see [Error handling](04-starting-a-davinci-flow.html#_error_handling).

## Handling SuccessNode

When the flow reaches `SuccessNode`, authentication has completed successfully.

Use `user()` to retrieve the active OIDC session through the client or the actions returned by `useDaVinci`.

Handle a successful DaVinci flow

```typescript
import { useEffect } from 'react';
import { useDaVinci } from '@ping-identity/rn-davinci';

function LoginScreen() {
  const { node, user } = useDaVinci(davinciClient);

  useEffect(() => {
    if (node?.type !== 'SuccessNode') return;

    async function loadSession() {
      const session = await user();

      if (session) {
        console.log('Access token:', session.accessToken);
        console.log('Refresh token:', session.refreshToken);
        console.log('Expires in:', session.expiresIn);
      }
    }

    void loadSession();
  }, [node, user]);
}
```

The `user()` method returns `null` if no active session is available.

See [Managing sessions and tokens in React Native](06-handling-sessions.html) for how to retrieve the user session and manage tokens.

## Handling specific collectors

Some collectors need handling beyond ordinary text, password, and selection fields. They map to different execution modes and may display information, trigger a flow branch, or report progress while the user completes an action elsewhere.

Use these pages for the collector-specific behavior:

* [Boolean collectors](collectors/boolean.html) use manual Boolean input and can enforce a required confirmation.

* [Read-only text collectors](collectors/read-only-text.html) display flow content without contributing a submission value.

* [Flow collectors](collectors/flow.html) trigger an immediate flow action and bypass other form values.

* [Polling collectors](collectors/polling.html) report asynchronous progress and terminal statuses.

* [QR code collectors](collectors/qr-code.html) display image data for an out-of-band step without submitting the image.

## Disposing an incomplete flow

If a user leaves a screen while a flow is in progress, call `dispose()` to release the native DaVinci instance:

Disposing an incomplete flow

```typescript
await davinciClient.dispose();
```

Calling `dispose()` releases the in-flight flow. It does not sign the user out or revoke tokens.
