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:

  • A blocks API key
  • A flow identifier and version number
  • A block identifier

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 three parts: a provider, hooks, and higher-order components (HOC).

DoptProvider

The DoptProvider allows you to leverage Dopt hooks and HOCs by providing access to the state of step blocks and group 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
flowVersions={{ 'user-onboarding': 2, 'upsell-flow': 4 }}
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 withBlock. 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 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:

const [block, intent] = useBlock('j0zExxZDVKCPXPzB2ZgpW');

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

const [{ version, state }, { complete }] = useBlock('j0zExxZDVKCPXPzB2ZgpW');

useOrderedGroup hook

The useOrderedGroup hook helps you bind ordered group state to your UI and also gives you access to methods for mutating group and child block state.

Here’s how using the hook might look like in your app:

const [group, intent] = useOrderedGroup('h4A-veTkyGwu2qVQfkNME');

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

const [{ state, blocks, getCompleted }, { next, prev, goTo, complete }] = useOrderedGroup('h4A-veTkyGwu2qVQfkNME');

useUnorderedGroup hook

The useUnorderedGroup hook helps you bind unordered group state to your UI and also gives you access to methods for mutating group state.

Here’s how using the hook might look like in your app:

const [group, intent] = useUnorderedGroup('tMnCFzogwovXkgmmaswLj');

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

const [{ state, blocks, getCompleted }, { complete }] = useUnorderedGroup('tMnCFzogwovXkgmmaswLj');

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:

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 }, { reset, complete }] = useFlow('new-user-onboarding');

withBlock HOC

The withBlock HOC helps you construct a component with access to block data and intent methods for a given step block.

Here’s how using the HOC might look like in your app:

function MyComponent(props) {
const { block, intent } = props;
return block.state.active && <Component />;
}

const MyComponentWithDopt = withBlock(MyComponent, 'j0zExxZDVKCPXPzB2ZgpW');

In this case, we’ve created a new component called <MyComponentWithDopt /> which will only render when the block state is active. A more complex example might involve reacting to the completed state, reacting to child block data, and attaching event handlers to the complete() intent method to mutate block state.

withOrderedGroup HOC

The withOrderedGroup HOC helps you construct a component with access to group data and intent methods for a given ordered group block.

Here’s how using the HOC might look like in your app:

function MyComponent(props) {
const { group, intent } = props;
return group.state.active && <Component />;
}

const MyComponentWithDopt = withOrderedGroup(MyComponent, 'h4A-veTkyGwu2qVQfkNME');

In this case, we’ve created a new component called <MyComponentWithDopt /> which will only render when the group state is active. A more complex example might involve reacting to the completed state, reacting to child block data, and attaching event handlers to the various ordered group intent methods to mutate group and child block state.

withUnorderedGroup HOC

The withUnorderedGroup HOC helps you construct a component with access to group data and intent methods for a given unordered group block.

Here’s how using the HOC might look like in your app:

function MyComponent(props) {
const { group, intent } = props;
return group.state.active && <Component />;
}

const MyComponentWithDopt = withUnorderedGroup(MyComponent, 'tMnCFzogwovXkgmmaswLj');

In this case, we’ve created a new component called <MyComponentWithDopt /> which will only render when the group state is active. A more complex example might involve reacting to the completed state and attaching event handlers to the complete() intent method to mutate group state.

withFlow HOC

The withFlow HOC helps you construct a component with access to flow data and intent methods for a given flow.

Here’s how using the HOC might look like in your app:

function MyComponent(props) {
const { flow, intent } = props;
return flow.state.started && <Component />;
}

const MyComponentWithDopt = withFlow(MyComponent, 'new-user-onboarding');

In this case, we’ve created a new component called <MyComponentWithDopt /> which will only render when the flow state is started. A more complex example might involve reacting to other states and attaching event handlers to the intent methods to mutate flow state.

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"
flowVersions={{ 'user-onboarding': 2 }}
>
<Application />
</DoptProvider>,
rootElement
);

We’ve hardcoded the user ID (0001) for demonstration purposes in the userId prop. Realistically, you should dynamically pass in the user ID 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 user-onboarding flow in the flowVersions prop. Learn more about flow versioning →

info

If you’d like to pull in the draft version of a flow, you can set the version number to 0.

Reacting to block state

Now that we’ve added DoptProvider to our app, we can use either the useBlock hook or withBlock HOC 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 step block. Basically, this boils down to showing the modal when the step 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 completed state as well.

The useBlock hook can help us access the active state:

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

export function Application() {
const [{ state }] = useBlock('HNWvcT78tyTwygnbzU6SW');
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 HNWvcT78tyTwygnbzU6SW step block. The <Modal /> is then set to conditionally render if state.active evaluates to true.

We can also use the withBlock HOC to do the same thing:

modal.tsx
import { Modal as RootModal } from '@your-company/modal';

export function Modal(props) {
const { block, children } = props;
const { state } = block;
return state.active && (
<RootModal>
{children}
</RootModal>
);
}

We’ve instrumented our <Modal /> to pull in a block prop which contains state that we want to react to from our step block. The <Modal /> is then set to conditionally render if state.active evaluates to true.

Next, we will construct a new component using withBlock and <Modal />.

application.tsx
import { withBlock } from '@dopt/react';
import { Modal } from './modal';

export function Application() {
const ModalWithDopt = withBlock(Modal, 'HNWvcT78tyTwygnbzU6SW');
return (
<main>
<ModalWithDopt>
<h1>👏 Welcome to our app!</h1>
<p>This is your onboarding experience!</p>
</ModalWithDopt>
</main>
);
}

We’ve created a new component called <ModalWithDopt /> that takes the block state from the HNWvcT78tyTwygnbzU6SW step block and passes it into <Modal /> from earlier.

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 in the modal sets the block to completed which should hide the modal.

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 }, { complete }] = useBlock('HNWvcT78tyTwygnbzU6SW');
return (
<main>
{state.active && <Modal>
<h1>👏 Welcome to our app!</h1>
<p>This is your onboarding experience!</p>
<button onClick={complete}>Close me</button>
</Modal>}
</main>
);
}

We’ve made two changes to the original example. First, we're pulling in the complete() intent method from the useBlock hook. This allows us to set the state of the HNWvcT78tyTwygnbzU6SW step block to completed.

info

Calling the complete() intent method will set a block as completed. This results in two block state side-effects: active is set to false and completed is set to true.

Second, we’ve added a <button /> with an onClick handler that is hooked up to the complete() intent method. This translates to a click on the button setting the active state to false which will hide our modal.

Let’s take a look at how we can accomplish the same thing with the withBlock HOC. We’ll build off of our previous code again:

modal.tsx
import { Modal as RootModal } from '@your-company/modal';

export function Modal(props) {
const { block, intent, children } = props;
const { state } = block;
const { complete } = intent;
return state.active && (
<RootModal>
{children}
<button onClick={complete}>Close me</button>
</RootModal>
);
}

We’ve made three changes to the original example. First, we’ve instrumented our <Modal /> to pull in an intent prop which gives us access to the block state intent methods.

Second, we’ve pulled out the complete() intent method specifically.

And third, we’ve added a <button /> with an onClick handler that is hooked up to the complete() intent method. This translates to a click on the button setting the active state to false which will hide our modal.

We don’t need to make any changes to the application.tsx file from the orignal example because all that it’s doing is creating the <ModalWithDopt /> component and rendering it.

Leveraging group state and intents

Accessing state and intents for groups looks just like accessing state and intents for step blocks. Let’s build a simple modal tour to showcase how to build with an ordered group.

Imagine we have a <Modal /> component that can show a series of onboarding content. We want to be able to show content for each of the three onboarding steps we have. These steps should show one at a time and also allow the user to move back and forth between them. The user should also be able to skip this modal completely. And lastly, we want to show a progress bar so the user is aware how far along they are in the tour.

On the Dopt side, this will be powered by an ordered group block with three step blocks inside.

First, let’s start by scaffolding out the <Modal /> with our content:

import { Modal } from '@your-company/modal';
import { useOrderedGroup } from '@dopt/react';

export function OnboardingModal() {
const [group, intent] = useOrderedGroup('vPufoBzj9wvCQG5VodeoK');
return (
{group.state.active &&
<Modal>
{group.blocks.map((step, index) => (
step.state.active && (
<div>
{index == 0 && (
<>
<h1>Welcome to our product</h1>
<p>You're going to love using it!</p>
</>
)}
{index == 1 && (
<>
<h1>Create objects to get started</h1>
<p>Objects are the core of the product!</p>
</>
)}
{index == 2 && (
<>
<h1>Invite teammates</h1>
<p>Collaborate with teammates to get things done!</p>
</>
)}
</div>
)
))}
</Modal>
}
)
}

We’ve done a few things here. First, we’re using the useOrderedGroup hook to access the group block using its unique identifier (vPufoBzj9wvCQG5VodeoK). This gives us access to group which contains the state of the group and data about the three steps that we have inside of the group.

Next, we’ve mapped overthose steps to show the content for a particular step when its state is active. Here we’re relying on the index of the steps in the group, but you can also conditionally render based on the block ID (step.sid).

Now, let’s hook up some actions to group intents to power progression through this modal tour.

import { Modal } from '@your-company/modal';
import { useOrderedGroup } from '@dopt/react';

export function OnboardingModal() {
const [group, intent] = useOrderedGroup('vPufoBzj9wvCQG5VodeoK');
return (
{group.state.active &&
<Modal>
{group.blocks.map((step, index) => (
step.state.active && (
<div>
{index == 0 && (
<>
<h1>Welcome to our product</h1>
<p>You're going to love using it!</p>
</>
)}
{index == 1 && (
<>
<h1>Create objects to get started</h1>
<p>Objects are the core of the product!</p>
</>
)}
{index == 2 && (
<>
<h1>Invite teammates</h1>
<p>Collaborate with teammates to get things done!</p>
</>
)}
</div>
)
))}
<button onClick={intent.next}>Next</button>
<button onClick={intent.prev}>Previous</button>
<button onClick={intent.complete}>Exit</button>
</Modal>
}
)
}

We’ve added three <button /> elements that map to the three actions we wanted to support. We set each <button /> elements’ onClick handler to the appropriate intent method.

The Next button is hooked up to the next() intent which sets the currently active step to active: false and completed: true and then sets the next step to active. This effectively progresses the state of the tour to the next step. If the user is on the last step and clicks this button, then the tour will end because the entire modal is predicated on group.state.active being true.

The Previous button is hooked up to the prev() intent which sets the currently active step to active: false and then sets the previous step to active. This effectively progresses the state of the tour to the previous step. If the user is on the first step and clicks this button, nothing will happen because there isn’t another step to go backwards to.

The Exit button is hooked up to the complete() intent which sets the state of the group to active: false and completed: true. This effectively stops the tour because the entire modal is predicated on group.state.active being true.

To wrap up our onboarding tour, let’s add a progress bar to show the user how far along they are in the tour.

import { Modal } from '@your-company/modal';
import { Progress } from '@your-company/progress';
import { useOrderedGroup } from '@dopt/react';

export function OnboardingModal() {
const [group, intent] = useOrderedGroup('vPufoBzj9wvCQG5VodeoK');
return (
{group.state.active &&
<Modal>
<Progress value={(group.getCompleted().length / group.size) * 100} />
{group.blocks.map((step, index) => (
step.state.active && (
<div>
{index == 0 && (
<>
<h1>Welcome to our product</h1>
<p>You're going to love using it!</p>
</>
)}
{index == 1 && (
<>
<h1>Create objects to get started</h1>
<p>Objects are the core of the product!</p>
</>
)}
{index == 2 && (
<>
<h1>Invite teammates</h1>
<p>Collaborate with teammates to get things done!</p>
</>
)}
</div>
)
))}
<button onClick={intent.next}>Next</button>
<button onClick={intent.prev}>Previous</button>
<button onClick={intent.complete}>Exit</button>
</Modal>
}
)
}

We’ve added a <Progress /> component to our modal that accepts a value prop from 0 to 100.

To get the numerator in our progress calculation, we use group.getComplete() to give us the steps that have been completed in the tour. Using the length array data property gives us the number of steps that have been completed.

To get the denominator in our progresss calculation, we use group.size which gives us the total number of steps in the tour.