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 reference 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, a hook, and a higher-order component (HOC).

DoptProvider

The DoptProvider allows you to leverage the useBlock hook and withBlock HOC by providing access to the state of step blocks for a given flow version in the context of an identified user.

The provider accepts three required props:

PropTypeDescription
userIdstring | undefinedThe ID of an identified user
apiKeystringA blocks API key
flowVersionsRecord<string, number>An object of flow identifiers and associated version numbers
note

If userId is undefined, all state values will default to false.

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.

info

For debugging purposes, you can add a logLevel prop to DoptProvider which sets the minimum log level printed to the console. This accepts the following values: 'trace', 'debug' 'info', 'warn', 'error', 'silent' (default).

info

DoptProvider has an experimental prop called optimisticUpdates (boolean) that will optimistically update the state of a block when an intent method is called. This defaults to true.

useBlock hook

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

The hook accepts a single required argument:

ArgumentTypeDescription
identifierstringThe reference ID for a step block

The hook returns the following block state for the given step block:

StateType
activeboolean
completedboolean
startedboolean
stoppedboolean
exitedboolean

The hook also returns the following intent methods for mutating block state:

MethodTypeDescription
start()() => voidSignals that the experience that this block powers has begun
complete()() => voidSignals that the experience that this block powers has finished
stop()() => voidSignals that the experience that this block powers has ended prematurely and ends the progress for this branch in the flow
exit()() => voidSignals an end of the entire flow

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

const [block, intent] = useBlock(referenceId);
tip

The example above uses destructuring assignment in JavaScript to expose variables for state and intent methods from the useBlock hook.

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

const [{ active, completed }, { start, complete }] = useBlock(referenceId);

withBlock HOC

The withBlock HOC (higher-order component) helps you construct a component with access to block state and intent methods for a given step block.

The HOC accepts two required arguments:

ArgumentTypeDescription
ComponentReact.ComponentType<T>A React component
identifierstringThe reference ID for a step block

The HOC will return a React component that has access to the same block state and intent methods that the useBlock hook returns.

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

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

const MyComponentWithDopt = withBlock(MyComponent, referenceId);

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 different states and attaching event handlers to intent methods to mutate block 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.

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 any of the states returned.

The useBlock hook can help us access the active state:

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

export function Application() {
const [{ active }] = useBlock("HNWvcT78tyTwygnbzU6SW");
return (
<main>
{active && <Modal>
<h1>👏 Welcome to our app!</h1>
<p>This is your onboarding experience!</p>
</Modal>}
</main>
);
}

We've pulled in the active state of the HNWvcT78tyTwygnbzU6SW step block. The <Modal /> is then set to conditionally render if the active variable 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 { active } = block;
return active && (
<RootModal>
{children}
</RootModal>
);
}

We've instrumented our <Modal /> to pull in a block prop which contains the active state we want to react to from our step block. The <Modal /> is then set to conditionally render if the active variable 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 [{ active }, { complete }] = useBlock("HNWvcT78tyTwygnbzU6SW");
return (
<main>
{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 finished. 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 { active } = block;
const { complete } = intent;
return 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.