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.