Skip to main content

Using the React SDK

Overview

This guide will walk you through the tools that the React SDK provides and examples on how to use them to build an onboarding experience.

Before we get started, you'll need:

Installation

You can install the React SDK into your React project with a JavaScript package manager:

npm install @dopt/react

Usage

The React SDK consists of two parts: a provider (DoptProvider) and hooks.

DoptProvider

The DoptProvider allows you to leverage Dopt hooks by providing access to the state of component blocks for a given flow version in the context of an identified user.

Here’s what the provider might look like in your app:

<DoptProvider
flows={{
'user-onboarding': 2,
upsell: 4,
'feature-callout': 0,
}}
userId={userId}
apiKey={blocksAPIKey}
>
<Application />
</DoptProvider>
info

While it’s common to place the provider at the root of your app, you can place it anywhere you’d like as long as it comes before where you would use useBlock or useFlow. You can even have multiple instances of the provider in various places in your app.

useBlock hook

The useBlock hook helps you bind block state and content to your UI and also gives you access to methods for mutating block state.

Here’s how using the hook might look like in your app for the twenty-llamas-attack block in the new-user-onboarding flow:

const [block, transition] = useBlock<['complete']>(
'new-user-onboarding.twenty-llamas-attack'
);
info

The <['complete']> portion in the useBlock instantiation is a TypeScript specific concept which allows you to enumerate the transitions associated with the block. This provides a bunch of utility in terms of error detection and code completion while developing with the SDK.

info

The example above uses the block identifier (twenty-llamas-attack) prefixed with the flow identifier (new-user-onboarding). However, you can also use the uid for the block without the flow identifier prefix instead.

You can further destructure the block variables to expose a subset of block data:

const [{ state, field }, transition] = useBlock<['complete']>(
'new-user-onboarding.twenty-llamas-attack'
);

useFlow hook

The useFlow hook helps you bind flow state to your UI and also gives you access to methods for mutating flow state.

Here’s how using the hook might look like in your app for the new-user-onboarding flow:

const [flow, intent] = useFlow('new-user-onboarding');

You can further destructure the flow and intent variables to expose a subset of flow data and intent methods:

const [{ blocks, version, state }, { finish, reset }] = useFlow(
'new-user-onboarding'
);

Example

Let’s walk through an example of how you might use the React SDK to build an onboarding experience.

Initializing DoptProvider

First, we’ll need to initialize the DoptProvider in our app.

import { DoptProvider } from '@dopt/react';
import Application from './application';

const rootElement = document.getElementById('root');
ReactDOM.render(
<DoptProvider
userId="0001"
apiKey="YOUR_BLOCKS_API_KEY"
flows={{ 'new-user-onboarding': 2 }}
>
<Application />
</DoptProvider>,
rootElement
);

We’ve hardcoded the user identifier (0001) for demonstration purposes in the userId prop. Realistically, you should dynamically pass in the user identifier that you used to identify the user who will be seeing this onboarding experience.

We’ve added our blocks API key to the apiKey prop.

And finally, we’ve defined that we want to pull in version 2 of the new-user-onboarding flow in the flows prop. Learn more about flow versioning →

info

If you’d like to pull in the uncommitted version of a flow, you can set the version tag to "uncommitted" or 0. This can help while you’re actively developing a flow.

info

If your user’s identifier isn’t ready yet, for example, because an async dependency hasn’t finished fetching, you can pass in userId={undefined}, and Dopt's hooks will return sensible defaults. When you subsequently pass in a defined value like userId={user.identifier}, Dopt will update and trigger all hooks with updated flow and block values.

Reacting to block state

Now that we’ve added DoptProvider to our app, we can use the useBlock hook to bind our UI to block state.

Let’s walk through an example of conditionally rendering a modal based off of the active state of a custom component block. Basically, this boils down to showing the modal when the custom component block active state is true and hiding it when the active state is false.

info

This example showcases reacting to the active block state, but you can hook up your UI to the entered and exited states as well.

The useBlock hook helps us access the active state:

import { useBlock } from '@dopt/react';
import { Modal } from './modal';

export function Application() {
const [{ state }] = useBlock<['complete']>(
'new-user-onboarding.twenty-llamas-attack'
);
return (
<main>
{state.active && (
<Modal>
<h1>👏 Welcome to our app!</h1>
<p>This is your onboarding experience!</p>
</Modal>
)}
</main>
);
}

We’ve pulled in the state of the twenty-llamas-attack custom component block. The <Modal /> is then set to conditionally render if state.active evaluates to true.

Mutating block state

Now that we’ve hooked up our modal to react to block state, let’s also set it up so that clicking a button hides the modal. Imagine that we’ve created a path named complete from the twenty-llamas-attack custom component block to the next block in our flow.

Let’s build off of our previous code using the useBlock hook:

import { useBlock } from '@dopt/react';
import { Modal } from './modal';

export function Application() {
const [{ state }, transition] = useBlock<['complete']>(
'new-user-onboarding.twenty-llamas-attack'
);
return (
<main>
{state.active && (
<Modal>
<h1>👏 Welcome to our app!</h1>
<p>This is your onboarding experience!</p>
<button onClick={() => transition('complete')}>Close me</button>
</Modal>
)}
</main>
);
}

We’ve made two changes to the original example. First, we're pulling in the transition() function from the useBlock hook. This allows us to transition the twenty-llamas-attack custom component block along it’s complete path which in turn will also update the block’s state to active: false and exited: true.

Second, we’ve added a <button /> with an onClick handler that calls the transition() function. Within the transition() function, we are passing in our complete path name to indicate that the user should transition along the complete path. This translates to a click on the button setting the active state to false which will hide our modal.

info

Calling transition('complete') will also update the block’s transitioned data to { complete: true } to reflect that the block was transitioned along its complete path.

Using fields

Pulling down field values is just as easy as using state. Let's imagine that we have configured text fields for a title, body, and button for our modal. We can access these fields using the field() accessor from the useBlock hook.

import { useBlock } from '@dopt/react';
import { Modal } from './modal';

export function Application() {
const [{ state, field }, transition] = useBlock<['complete']>(
'new-user-onboarding.twenty-llamas-attack'
);
return (
<main>
{state.active && (
<Modal>
<h1>{field('title')}</h1>
<p>{field('body')}</p>
<button onClick={() => transition('complete')}>
{field('button')}
</button>
</Modal>
)}
</main>
);
}

Note how we've replaced all of the static content with field() that references the field's identifier. When this modal renders, the field values that we define in Dopt will show up for in the <h1>, <p>, and <button>.