React

Examples & Recipes

This page provides practical examples and recipes for using OnboardJS in your React applications. See how to implement common onboarding patterns, customize step rendering, persist progress, and more.


1. Basic Linear Onboarding Flow

tsx
70 lines
1import { OnboardingProvider, useOnboarding } from '@onboardjs/react'
2
3const steps = [
4  {
5    id: 'welcome',
6    type: 'INFORMATION',
7    payload: { title: 'Welcome!' },
8    nextStep: 'profile',
9  },
10  {
11    id: 'profile',
12    type: 'SINGLE_CHOICE',
13    payload: {
14      question: 'Your role?',
15      options: [{ id: 'dev', label: 'Developer', value: 'dev' }],
16    },
17    nextStep: 'done',
18  },
19  { id: 'done', type: 'INFORMATION', payload: { title: 'All set!' } },
20]
21
22const componentRegistry = {
23  INFORMATION: ({ payload }) => <div>{payload.title}</div>,
24  SINGLE_CHOICE: ({ payload, next }) => (
25    <div>
26      <h2>{payload.question}</h2>
27      {payload.options.map((option) => (
28        <button key={option.id} onClick={() => next({ answer: option.value })}>
29          {option.label}
30        </button>
31      ))}
32    </div>
33  ),
34}
35
36function OnboardingUI() {
37  const { currentStep, state, next, previous } = useOnboarding()
38
39  if (state.isCompleted) return <div>Onboarding complete!</div>
40
41  const Component =
42    componentRegistry[currentStep.payload.componentKey ?? currentStep.type] ||
43    (() => <div>Unknown step type</div>)
44
45  return (
46    <div>
47      <Component
48        payload={currentStep.payload}
49        coreContext={state.context}
50        onDataChange={() => {}}
51      />
52      <div>
53        <button onClick={previous} disabled={!state.canGoPrevious}>
54          Back
55        </button>
56        <button onClick={next} disabled={!state.canGoNext}>
57          Next
58        </button>
59      </div>
60    </div>
61  )
62}
63
64export default function App() {
65  return (
66    <OnboardingProvider steps={steps}>
67      <OnboardingUI />
68    </OnboardingProvider>
69  )
70}

2. Branching Flow Based on User Input

tsx
35 lines
1const steps = [
2  {
3    id: 'start',
4    type: 'INFORMATION',
5    payload: { title: 'Start' },
6    nextStep: 'choose',
7  },
8  {
9    id: 'choose',
10    type: 'SINGLE_CHOICE',
11    payload: {
12      question: 'Are you a developer?',
13      dataKey: 'isDeveloper',
14      options: [
15        { id: 'yes', label: 'Yes', value: true },
16        { id: 'no', label: 'No', value: false },
17      ],
18    },
19    nextStep: (context) =>
20      context.answers?.isDeveloper ? 'dev-setup' : 'user-setup',
21  },
22  {
23    id: 'dev-setup',
24    type: 'INFORMATION',
25    payload: { title: 'Dev Setup' },
26    nextStep: 'done',
27  },
28  {
29    id: 'user-setup',
30    type: 'INFORMATION',
31    payload: { title: 'User Setup' },
32    nextStep: 'done',
33  },
34  { id: 'done', type: 'INFORMATION', payload: { title: 'Finished!' } },
35]

3. Custom Step Component

tsx
26 lines
1function CustomSurveyStep({ step, payload, next, updateContext }) {
2  const [answer, setAnswer] = React.useState('')
3
4  const handleSubmit = async () => {
5    await updateContext({ survey: answer })
6    await next()
7  }
8
9  return (
10    <div>
11      <h2>{payload.title}</h2>
12      <input value={answer} onChange={(e) => setAnswer(e.target.value)} />
13      <button onClick={handleSubmit}>Continue</button>
14    </div>
15  )
16}
17
18const componentRegistry = {
19  CUSTOM_SURVEY: CustomSurveyStep,
20  // ...other mappings
21}
22
23<OnboardingProvider steps={steps}>
24  {/** Render the onboarding UI with custom components like above */}
25  <OnboardingUI />
26</OnboardingProvider>

4. Persisting Progress with localStorage

tsx
9 lines
1<OnboardingProvider
2  steps={steps}
3  localStoragePersistence={{
4    key: 'onboardjs:my-onboarding',
5    ttl: 1000 * 60 * 60 * 24, // 1 day
6  }}
7>
8  <OnboardingUI />
9</OnboardingProvider>

5. Integrating with Supabase or Neon

tsx
19 lines
1<OnboardingProvider
2  steps={steps}
3  customOnDataLoad={async () => {
4    const { data } = await supabase
5      .from('onboarding')
6      .select('context')
7      .eq('user_id', user.id)
8      .single()
9    return data?.context || undefined
10  }}
11  customOnDataPersist={async (context) => {
12    await supabase.from('onboarding').upsert({ user_id: user.id, context })
13  }}
14  customOnClearPersistedData={async () => {
15    await supabase.from('onboarding').delete().eq('user_id', user.id)
16  }}
17>
18  <OnboardingUI />
19</OnboardingProvider>

6. Handling Flow Completion

tsx
13 lines
1<OnboardingProvider
2  steps={steps}
3  onFlowComplete={(context) => {
4    // Send analytics, redirect, or show a custom message
5    console.log('Onboarding finished!', context)
6    toast('Onboarding Complete!', {
7      description: `Welcome, ${context.flowData?.userName || 'friend'}! You're all set.`,
8      duration: 3000,
9    })
10  }}
11>
12  <OnboardingUI />
13</OnboardingProvider>

7. Using Checklist Steps

tsx
16 lines
1const steps = [
2  {
3    id: 'checklist',
4    type: 'CHECKLIST',
5    payload: {
6      dataKey: 'setupTasks',
7      items: [
8        { id: 'profile', label: 'Complete your profile', isMandatory: true },
9        { id: 'invite', label: 'Invite a teammate', isMandatory: false },
10      ],
11      minItemsToComplete: 1,
12    },
13    nextStep: 'done',
14  },
15  { id: 'done', type: 'INFORMATION', payload: { title: 'All done!' } },
16]

8. Customizing Navigation Buttons

tsx
15 lines
1function CustomNav() {
2  const { state, next, previous, skip } = useOnboarding()
3
4  return (
5    <div>
6      <button onClick={previous} disabled={!state.canGoPrevious}>
7        Back
8      </button>
9      {state.isSkippable && <button onClick={skip}>Skip</button>}
10      <button onClick={next} disabled={!state.canGoNext}>
11        Next
12      </button>
13    </div>
14  )
15}

9. Accessing and Updating Context

tsx
12 lines
1function ShowUserName() {
2  const { state, updateContext } = useOnboarding()
3
4  return (
5    <div>
6      <p>User: {state.context.currentUser?.name}</p>
7      <button onClick={() => updateContext({ currentUser: { name: 'Soma' } })}>
8        Set Name to Soma
9      </button>
10    </div>
11  )
12}

10. Resetting the Onboarding Flow

tsx
5 lines
1function ResetButton() {
2  const { reset } = useOnboarding()
3
4  return <button onClick={() => reset()}>Restart Onboarding</button>
5}

More Recipes

More recipes are coming soon! If you have specific use cases or patterns you'd like to see, please open an issue on our GitHub repository.

Previous
Render step content