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
- yarn
- pnpm
npm install @dopt/react
yarn add @dopt/react
pnpm add @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>
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 →
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
.
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:
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 />
.
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.
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:
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.