---
title: Navigating an authentication journey in React Native
description: Handle each JourneyNode type returned during a React Native authentication journey, including ContinueNode, SuccessNode, FailureNode, and ErrorNode
component: orchsdks
page_id: orchsdks:journey:usage/react-native/05-navigating-an-authentication-journey
canonical_url: https://developer.pingidentity.com/orchsdks/journey/usage/react-native/05-navigating-an-authentication-journey.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: Wed, 6 May 2026 00:00:00 +0000
section_ids:
  handling_continuenode: Handling ContinueNode
  useJourneyForm: Normalising callbacks 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
  explictCallbacks: Handling callbacks explicitly by type
  handling_failurenode_and_errornode: Handling FailureNode and ErrorNode
  handling_successnode: Handling SuccessNode
---

# Navigating an authentication journey in React Native

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

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

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

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

* [Start](04-starting-an-authentication-journey.html)

* **Navigate**

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

***

Each step in an authentication journey, including the first, returns a `JourneyNode` object representing where you are in the journey.

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

**The node types returned during a journey**

| Node type      | Description                                                                                                                                                                     |
| -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `ContinueNode` | Indicates a step in the middle of the authentication journey that requires input.Call `actions.next(input)` to submit the collected payload and advance to the next step.       |
| `SuccessNode`  | Indicates successful authentication.Access the user object by calling `user()`.                                                                                                 |
| `FailureNode`  | Represents an unexpected failure, such as a network error, a parsing issue, or an internal SDK error.Access the failure details via `node.cause`.                               |
| `ErrorNode`    | Indicates a server-side validation or request error, such as invalid credentials or exceeding the maximum number of login attempts.Access the error message via `node.message`. |

To complete an authentication journey you must handle each node type in your client application.

Handling different node types in a journey

```typescript
import { useJourney } from '@ping-identity/rn-journey';

function LoginScreen() {
  const [node, actions] = useJourney();

  useEffect(() => {
    actions.start('sdkUsernamePasswordJourney');
  }, []);

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

  if (node?.type === 'ContinueNode') {
    // Render input fields for node.callbacks
  }

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

  if (node?.type === 'FailureNode') {
    // Display node.cause to the user or a generic error message
  }

  if (node?.type === 'ErrorNode') {
    // Display node.message to the user
  }
}
```

## Handling ContinueNode

For `ContinueNode` steps, the server sends a list of callbacks that require user input.

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

* [Normalising callbacks into form fields](#useJourneyForm)

  The `useJourneyForm` hook maps all callbacks to a flat list of typed `field.kind` values, such as text, password, boolean, choice, and so on, so your UI is driven by what kind of input is needed, not which specific callback the server sent.

  If the server journey is reconfigured to swap one callback type for another that maps to the same kind, the UI keeps working 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, so you don't have to write your own required-field checks.

  * Default values are pre-seeded. The hook hydrates `form.values` from `field.defaultValue` automatically, so pre-populated server fields just appear.

  * `form.meta` flags handle any mixed steps. When a step contains a mix of manual fields, FIDO callbacks, and output-only labels, the `hasManual`, `hasIntegrationRequired`, and other flags let you conditionally render the right UI sections without examining each callback yourself.

  * Submit payload is built for you. `form.input` is the ready-to-send payload, so you never assemble the callback array manually.

  * Less boilerplate. The code is shorter and doesn't grow linearly as the number of callback types increases.

* [Handling callbacks explicitly by type](#explictCallbacks)

  You work directly with callback objects using the same `callback.type` names, such as `NameCallback`,`PasswordCallback`.

  If you have worked with the native Orchestration SDK for Android or iOS previously you will find this pattern immediately recognisable.

  Other benefits:

  * No abstraction to learn. There are no normalised kind values or `executionMode` concepts to understand — the code says exactly what it does.

  * Full control over the payload. You set `callback.value` directly on the callback object and pass `{ callbacks }` to `actions.next()`. What you send is exactly what you set, with no intermediary building the payload.

  * Easier to handle unusual or custom callbacks. If a journey uses a non-standard callback that `useJourneyForm` does not support, explicit handling is the only way forward.

### Normalising callbacks into form fields

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

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

Collecting user input with useJourneyForm

```typescript
const [node, actions] = useJourney();
const form = useJourneyForm(node);

if (actions.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.id}
                value={Boolean(form.values[field.id] ?? false)}
                onValueChange={val ⇒ form.setValue(field.id, val)}
              />
            );
          }

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

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

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

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

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

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

return null;
```

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

**JourneyNormalizedField properties**

| Property            | Type                   | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      |
| ------------------- | ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `id`                | `string`               | A stable identifier for the field in the format `CallbackType:index`.For example, `NameCallback:0`.                                                                                                                                                                                                                                                                                                                                                                                                              |
| `prompt`            | `string`               | The label to display to the user.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                |
| `kind`              | `string`               | The input kind. One of:- `text`

- `password`

- `number`

- `boolean`

- `choice`

- `kba`

- `output`

- `unknown`                                                                                                                                                                                                                                                                                                                                                                                             |
| `executionMode`     | `string`               | How the field should be handled. One of:- `manual`

  A field to show to the user to obtain input.

- `auto_capable`

  A field that can be handled automatically without input from the user.

- `integration_required`

  A field that requires a separate integration, such as FIDO or social sign-on.

- `output_only`

  A read-only field such as a label or other text. No user input is required.

- `unsupported`

  A field that the Orchestration SDK for React Native does not recognize or support. |
| `requiresUserInput` | `boolean`              | Is `true` if the field needs the user to provide a value before the form can be submitted.                                                                                                                                                                                                                                                                                                                                                                                                                       |
| `required`          | `boolean`              | Is `true` if the field must have a non-empty value.                                                                                                                                                                                                                                                                                                                                                                                                                                                              |
| `defaultValue`      | varies                 | The pre-populated value from the server, if any.                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |
| `options`           | `JourneyFieldOption[]` | The available options to show the user when the `kind` property is `choice` or `kba`.Each option has an `index`, `label`, and `value` property.                                                                                                                                                                                                                                                                                                                                                                  |

#### Checking the composition of the form

The `useJourneyForm` 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 = useJourneyForm(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 such as FIDO is needed
if (form.meta.hasIntegrationRequired) {
  // Show an informational message
}
```

**All form.meta flags**

| Flag                        | Description                                                                                        |
| --------------------------- | -------------------------------------------------------------------------------------------------- |
| `hasManual`                 | `true` if at least one field requires direct user input.                                           |
| `hasOutputOnly`             | `true` if at least one field is display-only (no input required).                                  |
| `hasAutoCapable`            | `true` if at least one field can be handled automatically by the client.                           |
| `hasIntegrationRequired`    | `true` if at least one field requires an external integration (for example, FIDO or a social IdP). |
| `hasUnsupported`            | `true` if the step contains a callback type the SDK does not support.                              |
| `hasRequiredConsentMissing` | `true` if a consent callback is present and the user has not yet accepted.                         |

#### Checking if the required data is complete

Use the `form.canSubmit` boolean to check that each callback has been handled appropriately. If the boolean is `false`, you can check `form.issues` to list the reasons.

Possible reasons that you could not submit the form include the following:

**Reasons preventing form submission**

| Issue code                 | When it's raised                                                                                                                                                                                                             |
| -------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `NO_ACTIVE_CONTINUE_NODE`  | Either `node` is `null` or not the `ContinueNode` type.                                                                                                                                                                      |
| `INTEGRATION_REQUIRED`     | A callback has an `executionMode` value of `auto_capable` or `integration_required` that needs to be completed.Callbacks that require integration include those used for FIDO, social login, reCAPTCHA, and PingOne Protect. |
| `UNSUPPORTED_CALLBACK`     | A callback type the Orchestration SDK doesn't recognise.These callbacks have an `executionMode` value of `unsupported`.                                                                                                      |
| `REQUIRED_CONSENT_MISSING` | A callback marked as required is not marked as `true`.For example, a `TermsAndConditions`, `ConsentMapping`, or `BooleanAttributeInput` callbacks marked as `true: required`.                                                |
| `INVALID_VALUE`            | Field validation has failed.For example, a required field is empty, a number field has a non-finite value, a choice field has an out-of-range index, or a KBA field is missing either the question or answer.                |

|   |                                                                                                                                                                |
| - | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|   | The `useJourneyForm` hook is headless. It manages normalized fields and submit planning, but does not render UI by itself, and it does not auto-run callbacks. |

### Handling callbacks explicitly by type

If you prefer you can handle each callback type explicitly and work directly with `node.callbacks`.

You'll need to build up the callback response yourself, for example setting the value the user enters into a form field, and return the complete data to the server by using `actions.next({ callbacks });`

Handling callbacks explicitly

```typescript
const [node, actions] = useJourney();

if (actions.loading) return <ActivityIndicator />;

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

  return (
    <View>
      {callbacks.map((callback, index) => {

        if (callback.type === 'NameCallback') {
          return (
            <TextInput
              key={index}
              placeholder={callback.prompt}
              onChangeText={text => {
                callback.value = text;
              }}
            />
          );
        }

        if (callback.type === 'PasswordCallback') {
          return (
            <TextInput
              key={index}
              placeholder={callback.prompt}
              secureTextEntry
              onChangeText={text => {
                callback.value = text;
              }}
            />
          );
        }

        return null;
      })}

      <Button
        title="Continue"
        onPress={() => actions.next({ callbacks })}
      />
    </View>
  );
}

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

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

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

return null;
```

## Handling FailureNode and ErrorNode

The **Journey** module distinguishes between the `FailureNode` and `ErrorNode` types for different categories of errors encountered during the authentication flow.

* `FailureNode`

  Indicates an unexpected issue that prevents the journey from continuing. This could stem from network problems or data parsing errors.

  You can access the underlying error using `node.cause` 1.

  You should display a user-friendly generic error message and log the details for support investigation.

* `ErrorNode`

  Signifies an error response from the authentication server, typically an HTTP `4xx` or `5xx` status code.

  These errors often relate to invalid user input or issues server-side.

  You can retrieve the specific error message provided by the server using `node.message` 2, and access the raw JSON response by using `node.input` 3.

Handling error and failure node types in a journey

```typescript
import { useJourney } from '@ping-identity/rn-journey';

function LoginScreen() {
  const [node, actions] = useJourney();

  useEffect(() => {
    actions.start('sdkUsernamePasswordJourney');
  }, []);

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

  if (node?.type === 'ContinueNode') {
    // Render input fields for node.callbacks
  }

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

  if (node?.type === 'FailureNode') {
    // Display a user-friendly message to the user
    const cause = node.cause; (1)
    console.error('Authentication failed:', cause);
  }

  if (node?.type === 'ErrorNode') {
    // Display a user-friendly message to the user
    const message = node.message; (2)
    const rawInput = node.input; (3)
    console.error('Journey error:', message);
    console.error('Server response:', JSON.stringify(rawInput));
  }
}
```

## Handling SuccessNode

When the client reaches the `SuccessNode` type it securely stores the session token. However, the `SuccessNode` does not carry token data inline.

Call `actions.user()` or `actions.ssoToken()` explicitly to retrieve session information. This keeps sensitive token data out of SDK-managed state until your app requests it.

```typescript
import { useJourney } from '@ping-identity/rn-journey';

function LoginScreen() {
  const [node, actions] = useJourney();

  useEffect(() => {
    actions.start('sdkUsernamePasswordJourney');
  }, []);

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

    async function fetchSession() {
      const user = await actions.user();
      const session = await actions.ssoToken();
    }

    void fetchSession();
  }, [node]);

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

  if (node?.type === 'ContinueNode') {
    // Render input fields for node.callbacks
  }

  if (node?.type === 'FailureNode') {
    // Display node.cause to the user or a generic error message
  }

  if (node?.type === 'ErrorNode') {
    // Display node.message to the user
  }
}
```

When the journey completes successfully, see [Managing sessions and tokens in React Native](06-handling-sessions.html) for how to retrieve the user object and manage tokens.
