onboardjs-react

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

OnboardJS React Integration

OnboardJS 与 React 集成

OnboardJS is a headless library for building user onboarding experiences. You control the UI; OnboardJS handles flow logic, state, persistence, and navigation.
OnboardJS是一个用于构建用户引导体验的无头库。您负责UI部分;OnboardJS处理流程逻辑、状态、持久化和导航。

Before Starting

开始之前

1. Verify React project - Check for
package.json
with React dependencies. If not a React project, inform the user.
2. Detect package manager - Check for lock files to determine the correct install command:
Lock FilePackage ManagerInstall Command
pnpm-lock.yaml
pnpm
pnpm add @onboardjs/core @onboardjs/react
yarn.lock
yarn
yarn add @onboardjs/core @onboardjs/react
bun.lockb
bun
bun add @onboardjs/core @onboardjs/react
package-lock.json
or none
npm
npm install @onboardjs/core @onboardjs/react
3. Detect Next.js - Check for
next.config.js
,
next.config.mjs
, or
next
in dependencies. If Next.js, see Next.js Setup section.
1. 验证React项目 - 检查是否存在包含React依赖的
package.json
。如果不是React项目,请告知用户。
2. 检测包管理器 - 检查锁文件以确定正确的安装命令:
锁文件包管理器安装命令
pnpm-lock.yaml
pnpm
pnpm add @onboardjs/core @onboardjs/react
yarn.lock
yarn
yarn add @onboardjs/core @onboardjs/react
bun.lockb
bun
bun add @onboardjs/core @onboardjs/react
package-lock.json
或无
npm
npm install @onboardjs/core @onboardjs/react
3. 检测Next.js - 检查是否存在
next.config.js
next.config.mjs
或依赖中的
next
。如果是Next.js项目,请查看Next.js 配置章节。

Installation

安装

bash
undefined
bash
undefined

npm

npm

npm install @onboardjs/core @onboardjs/react
npm install @onboardjs/core @onboardjs/react

pnpm

pnpm

pnpm add @onboardjs/core @onboardjs/react
pnpm add @onboardjs/core @onboardjs/react

yarn

yarn

yarn add @onboardjs/core @onboardjs/react
yarn add @onboardjs/core @onboardjs/react

bun

bun

bun add @onboardjs/core @onboardjs/react
undefined
bun add @onboardjs/core @onboardjs/react
undefined

Quick Setup

快速配置

1. Define Steps with Components

1. 用组件定义步骤

tsx
// steps.tsx
import { OnboardingStep } from '@onboardjs/react'
import { WelcomeStep } from './components/WelcomeStep'
import { ProfileFormStep } from './components/ProfileFormStep'
import { CompleteStep } from './components/CompleteStep'

const steps: OnboardingStep[] = [
  {
    id: 'welcome',
    component: WelcomeStep,
    payload: { title: 'Welcome!', description: 'Let\'s get started' },
    nextStep: 'profile'
  },
  {
    id: 'profile',
    component: ProfileFormStep,
    nextStep: 'complete'
  },
  {
    id: 'complete',
    component: CompleteStep,
    payload: { title: 'All done!' },
    nextStep: null
  }
]
tsx
// steps.tsx
import { OnboardingStep } from '@onboardjs/react'
import { WelcomeStep } from './components/WelcomeStep'
import { ProfileFormStep } from './components/ProfileFormStep'
import { CompleteStep } from './components/CompleteStep'

const steps: OnboardingStep[] = [
  {
    id: 'welcome',
    component: WelcomeStep,
    payload: { title: 'Welcome!', description: 'Let\'s get started' },
    nextStep: 'profile'
  },
  {
    id: 'profile',
    component: ProfileFormStep,
    nextStep: 'complete'
  },
  {
    id: 'complete',
    component: CompleteStep,
    payload: { title: 'All done!' },
    nextStep: null
  }
]

2. Wrap with Provider

2. 用Provider包裹组件

tsx
import { OnboardingProvider } from '@onboardjs/react'
import { steps } from './steps'

function App() {
  return (
    <OnboardingProvider
      steps={steps}
      onFlowComplete={(ctx) => console.log('Done!', ctx)}
    >
      <OnboardingUI />
    </OnboardingProvider>
  )
}
tsx
import { OnboardingProvider } from '@onboardjs/react'
import { steps } from './steps'

function App() {
  return (
    <OnboardingProvider
      steps={steps}
      onFlowComplete={(ctx) => console.log('Done!', ctx)}
    >
      <OnboardingUI />
    </OnboardingProvider>
  )
}

3. Use the Hook

3. 使用钩子

tsx
import { useOnboarding } from '@onboardjs/react'

function OnboardingUI() {
  const { renderStep, next, previous, state, loading } = useOnboarding()

  if (loading.isHydrating) return <Spinner />
  if (state?.isCompleted) return <CompletedScreen />

  return (
    <div>
      {renderStep()}
      <div>
        <button onClick={previous} disabled={state?.isFirstStep}>Back</button>
        <button onClick={() => next()} disabled={!state?.canGoNext}>
          {state?.isLastStep ? 'Finish' : 'Next'}
        </button>
      </div>
    </div>
  )
}
tsx
import { useOnboarding } from '@onboardjs/react'

function OnboardingUI() {
  const { renderStep, next, previous, state, loading } = useOnboarding()

  if (loading.isHydrating) return <Spinner />
  if (state?.isCompleted) return <CompletedScreen />

  return (
    <div>
      {renderStep()}
      <div>
        <button onClick={previous} disabled={state?.isFirstStep}>Back</button>
        <button onClick={() => next()} disabled={!state?.canGoNext}>
          {state?.isLastStep ? 'Finish' : 'Next'}
        </button>
      </div>
    </div>
  )
}

Step Indicator / Progress

步骤指示器/进度

Important: The
state
object does NOT have a
steps
array. Use
currentStepNumber
and
totalSteps
, or calculate from step IDs.
重要提示:
state
对象没有
steps
数组。请使用
currentStepNumber
totalSteps
,或通过步骤ID计算。

Option 1: Use Built-in Properties (if available)

选项1:使用内置属性(如果可用)

tsx
function OnboardingUI() {
  const { state, renderStep } = useOnboarding()

  return (
    <div>
      {state?.currentStepNumber && state?.totalSteps && (
        <div>Step {state.currentStepNumber} of {state.totalSteps}</div>
      )}
      <progress
        value={state?.currentStepNumber ?? 0}
        max={state?.totalSteps ?? 1}
      />
      {renderStep()}
    </div>
  )
}
tsx
function OnboardingUI() {
  const { state, renderStep } = useOnboarding()

  return (
    <div>
      {state?.currentStepNumber && state?.totalSteps && (
        <div>Step {state.currentStepNumber} of {state.totalSteps}</div>
      )}
      <progress
        value={state?.currentStepNumber ?? 0}
        max={state?.totalSteps ?? 1}
      />
      {renderStep()}
    </div>
  )
}

Option 2: Calculate from Step IDs (recommended)

选项2:通过步骤ID计算(推荐)

For reliable progress tracking, define step IDs once and calculate the index:
tsx
// steps.tsx - export your step IDs
export const STEP_IDS = ['welcome', 'profile', 'preferences', 'complete'] as const

export const steps: OnboardingStep[] = [
  { id: 'welcome', component: WelcomeStep, nextStep: 'profile' },
  { id: 'profile', component: ProfileStep, nextStep: 'preferences' },
  { id: 'preferences', component: PreferencesStep, nextStep: 'complete' },
  { id: 'complete', component: CompleteStep, nextStep: null }
]
tsx
// OnboardingUI.tsx
import { STEP_IDS } from './steps'

function OnboardingUI() {
  const { state, renderStep } = useOnboarding()

  const currentIndex = STEP_IDS.findIndex(id => id === state?.currentStep?.id)
  const currentStepNumber = currentIndex + 1
  const totalSteps = STEP_IDS.length

  return (
    <div>
      <div>Step {currentStepNumber} of {totalSteps}</div>
      <progress value={currentStepNumber} max={totalSteps} />
      {renderStep()}
    </div>
  )
}
为了可靠的进度追踪,请一次性定义步骤ID并计算索引:
tsx
// steps.tsx - 导出步骤ID
export const STEP_IDS = ['welcome', 'profile', 'preferences', 'complete'] as const

export const steps: OnboardingStep[] = [
  { id: 'welcome', component: WelcomeStep, nextStep: 'profile' },
  { id: 'profile', component: ProfileStep, nextStep: 'preferences' },
  { id: 'preferences', component: PreferencesStep, nextStep: 'complete' },
  { id: 'complete', component: CompleteStep, nextStep: null }
]
tsx
// OnboardingUI.tsx
import { STEP_IDS } from './steps'

function OnboardingUI() {
  const { state, renderStep } = useOnboarding()

  const currentIndex = STEP_IDS.findIndex(id => id === state?.currentStep?.id)
  const currentStepNumber = currentIndex + 1
  const totalSteps = STEP_IDS.length

  return (
    <div>
      <div>Step {currentStepNumber} of {totalSteps}</div>
      <progress value={currentStepNumber} max={totalSteps} />
      {renderStep()}
    </div>
  )
}

Step Indicator Component

步骤指示器组件

tsx
interface StepIndicatorProps {
  stepIds: readonly string[]
  currentStepId: string | undefined
}

function StepIndicator({ stepIds, currentStepId }: StepIndicatorProps) {
  const currentIndex = stepIds.findIndex(id => id === currentStepId)

  return (
    <div className="flex gap-2">
      {stepIds.map((id, index) => (
        <div
          key={id}
          className={`w-3 h-3 rounded-full ${
            index < currentIndex ? 'bg-green-500' :
            index === currentIndex ? 'bg-blue-500' :
            'bg-gray-300'
          }`}
        />
      ))}
    </div>
  )
}

// Usage
<StepIndicator stepIds={STEP_IDS} currentStepId={state?.currentStep?.id} />
tsx
interface StepIndicatorProps {
  stepIds: readonly string[]
  currentStepId: string | undefined
}

function StepIndicator({ stepIds, currentStepId }: StepIndicatorProps) {
  const currentIndex = stepIds.findIndex(id => id === currentStepId)

  return (
    <div className="flex gap-2">
      {stepIds.map((id, index) => (
        <div
          key={id}
          className={`w-3 h-3 rounded-full ${
            index < currentIndex ? 'bg-green-500' :
            index === currentIndex ? 'bg-blue-500' :
            'bg-gray-300'
          }`}
        />
      ))}
    </div>
  )
}

// 用法
<StepIndicator stepIds={STEP_IDS} currentStepId={state?.currentStep?.id} />

Step Component Pattern

步骤组件模式

tsx
import { StepComponentProps } from '@onboardjs/react'

interface ProfilePayload {
  title: string
  fields: string[]
}

const ProfileFormStep: React.FC<StepComponentProps<ProfilePayload>> = ({
  payload,
  context,
  onDataChange,
  initialData
}) => {
  const [name, setName] = useState(initialData?.name || '')

  const handleChange = (value: string) => {
    setName(value)
    onDataChange?.({ name: value }, value.length > 0)
  }

  return (
    <div>
      <h2>{payload.title}</h2>
      <input value={name} onChange={(e) => handleChange(e.target.value)} />
    </div>
  )
}
tsx
import { StepComponentProps } from '@onboardjs/react'

interface ProfilePayload {
  title: string
  fields: string[]
}

const ProfileFormStep: React.FC<StepComponentProps<ProfilePayload>> = ({
  payload,
  context,
  onDataChange,
  initialData
}) => {
  const [name, setName] = useState(initialData?.name || '')

  const handleChange = (value: string) => {
    setName(value)
    onDataChange?.({ name: value }, value.length > 0)
  }

  return (
    <div>
      <h2>{payload.title}</h2>
      <input value={name} onChange={(e) => handleChange(e.target.value)} />
    </div>
  )
}

Next.js Setup

Next.js 配置

OnboardJS uses React hooks and browser APIs, requiring client-side rendering in Next.js App Router.
OnboardJS使用React钩子和浏览器API,在Next.js App Router中需要客户端渲染。

Step Components - Add "use client"

步骤组件 - 添加"use client"

All step components must be client components:
tsx
// components/WelcomeStep.tsx
'use client'

import { StepComponentProps } from '@onboardjs/react'

export const WelcomeStep: React.FC<StepComponentProps> = ({ payload }) => {
  return <h1>{payload.title}</h1>
}
所有步骤组件必须是客户端组件:
tsx
// components/WelcomeStep.tsx
'use client'

import { StepComponentProps } from '@onboardjs/react'

export const WelcomeStep: React.FC<StepComponentProps> = ({ payload }) => {
  return <h1>{payload.title}</h1>
}

Provider Wrapper - Add "use client"

Provider包装器 - 添加"use client"

Create a client wrapper for the provider:
tsx
// components/OnboardingWrapper.tsx
'use client'

import { OnboardingProvider } from '@onboardjs/react'
import { steps } from './steps'

export function OnboardingWrapper({ children }: { children: React.ReactNode }) {
  return (
    <OnboardingProvider
      steps={steps}
      onFlowComplete={(ctx) => console.log('Done!', ctx)}
    >
      {children}
    </OnboardingProvider>
  )
}
为Provider创建一个客户端包装器:
tsx
// components/OnboardingWrapper.tsx
'use client'

import { OnboardingProvider } from '@onboardjs/react'
import { steps } from './steps'

export function OnboardingWrapper({ children }: { children: React.ReactNode }) {
  return (
    <OnboardingProvider
      steps={steps}
      onFlowComplete={(ctx) => console.log('Done!', ctx)}
    >
      {children}
    </OnboardingProvider>
  )
}

Use in Page (App Router)

在页面中使用(App Router)

tsx
// app/onboarding/page.tsx
import { OnboardingWrapper } from '@/components/OnboardingWrapper'
import { OnboardingUI } from '@/components/OnboardingUI'

export default function OnboardingPage() {
  return (
    <OnboardingWrapper>
      <OnboardingUI />
    </OnboardingWrapper>
  )
}
tsx
// app/onboarding/page.tsx
import { OnboardingWrapper } from '@/components/OnboardingWrapper'
import { OnboardingUI } from '@/components/OnboardingUI'

export default function OnboardingPage() {
  return (
    <OnboardingWrapper>
      <OnboardingUI />
    </OnboardingWrapper>
  )
}

Dynamic Import (Optional - for code splitting)

动态导入(可选 - 用于代码拆分)

Use dynamic imports to reduce initial bundle size:
tsx
// app/onboarding/page.tsx
import dynamic from 'next/dynamic'

const OnboardingWrapper = dynamic(
  () => import('@/components/OnboardingWrapper').then(mod => mod.OnboardingWrapper),
  {
    ssr: false,
    loading: () => <div>Loading onboarding...</div>
  }
)

export default function OnboardingPage() {
  return <OnboardingWrapper><OnboardingUI /></OnboardingWrapper>
}
使用动态导入减小初始包体积:
tsx
// app/onboarding/page.tsx
import dynamic from 'next/dynamic'

const OnboardingWrapper = dynamic(
  () => import('@/components/OnboardingWrapper').then(mod => mod.OnboardingWrapper),
  {
    ssr: false,
    loading: () => <div>Loading onboarding...</div>
  }
)

export default function OnboardingPage() {
  return <OnboardingWrapper><OnboardingUI /></OnboardingWrapper>
}

Dynamic Step Components (Optional - for large flows)

动态步骤组件(可选 - 用于大型流程)

Lazy-load step components to reduce bundle size:
tsx
// steps.tsx
'use client'

import dynamic from 'next/dynamic'
import { OnboardingStep } from '@onboardjs/react'

const WelcomeStep = dynamic(() => import('./components/WelcomeStep').then(m => m.WelcomeStep))
const ProfileStep = dynamic(() => import('./components/ProfileStep').then(m => m.ProfileStep))
const CompleteStep = dynamic(() => import('./components/CompleteStep').then(m => m.CompleteStep))

export const steps: OnboardingStep[] = [
  { id: 'welcome', component: WelcomeStep, nextStep: 'profile' },
  { id: 'profile', component: ProfileStep, nextStep: 'complete' },
  { id: 'complete', component: CompleteStep, nextStep: null }
]
懒加载步骤组件以减小包体积:
tsx
// steps.tsx
'use client'

import dynamic from 'next/dynamic'
import { OnboardingStep } from '@onboardjs/react'

const WelcomeStep = dynamic(() => import('./components/WelcomeStep').then(m => m.WelcomeStep))
const ProfileStep = dynamic(() => import('./components/ProfileStep').then(m => m.ProfileStep))
const CompleteStep = dynamic(() => import('./components/CompleteStep').then(m => m.CompleteStep))

export const steps: OnboardingStep[] = [
  { id: 'welcome', component: WelcomeStep, nextStep: 'profile' },
  { id: 'profile', component: ProfileStep, nextStep: 'complete' },
  { id: 'complete', component: CompleteStep, nextStep: null }
]

Pages Router (Legacy)

Pages Router(旧版)

For Next.js Pages Router, no "use client" needed but disable SSR:
tsx
// pages/onboarding.tsx
import dynamic from 'next/dynamic'

const OnboardingFlow = dynamic(
  () => import('@/components/OnboardingFlow'),
  { ssr: false }
)

export default function OnboardingPage() {
  return <OnboardingFlow />
}
对于Next.js Pages Router,无需添加"use client"但要禁用SSR:
tsx
// pages/onboarding.tsx
import dynamic from 'next/dynamic'

const OnboardingFlow = dynamic(
  () => import('@/components/OnboardingFlow'),
  { ssr: false }
)

export default function OnboardingPage() {
  return <OnboardingFlow />
}

Persistence

持久化

localStorage (Simple)

localStorage(简单方式)

tsx
<OnboardingProvider
  steps={steps}
  localStoragePersistence={{ key: 'onboarding_v1', ttl: 604800000 }}
>
tsx
<OnboardingProvider
  steps={steps}
  localStoragePersistence={{ key: 'onboarding_v1', ttl: 604800000 }}
>

Custom Backend

自定义后端

tsx
<OnboardingProvider
  steps={steps}
  customOnDataLoad={async () => await fetchFromAPI()}
  customOnDataPersist={async (ctx) => await saveToAPI(ctx)}
  customOnClearPersistedData={async () => await clearAPI()}
>
tsx
<OnboardingProvider
  steps={steps}
  customOnDataLoad={async () => await fetchFromAPI()}
  customOnDataPersist={async (ctx) => await saveToAPI(ctx)}
  customOnClearPersistedData={async () => await clearAPI()}
>

Conditional Navigation

条件导航

tsx
import { RoleSelectStep } from './components/RoleSelectStep'
import { AdminSetupStep } from './components/AdminSetupStep'
import { UserSetupStep } from './components/UserSetupStep'

{
  id: 'role-select',
  component: RoleSelectStep,
  payload: {
    options: [
      { value: 'admin', label: 'Admin' },
      { value: 'user', label: 'User' }
    ]
  },
  nextStep: (ctx) => ctx.flowData.role === 'admin' ? 'admin-setup' : 'user-setup'
}
tsx
import { RoleSelectStep } from './components/RoleSelectStep'
import { AdminSetupStep } from './components/AdminSetupStep'
import { UserSetupStep } from './components/UserSetupStep'

{
  id: 'role-select',
  component: RoleSelectStep,
  payload: {
    options: [
      { value: 'admin', label: 'Admin' },
      { value: 'user', label: 'User' }
    ]
  },
  nextStep: (ctx) => ctx.flowData.role === 'admin' ? 'admin-setup' : 'user-setup'
}

Conditional Step Visibility

条件步骤可见性

tsx
{
  id: 'admin-setup',
  component: AdminSetupStep,
  condition: (ctx) => ctx.flowData.role === 'admin'
}
tsx
{
  id: 'admin-setup',
  component: AdminSetupStep,
  condition: (ctx) => ctx.flowData.role === 'admin'
}

Advanced: See References

进阶:参考文档

  • references/step-config.md - Full step configuration options
  • references/hooks-api.md - Complete useOnboarding API
  • references/patterns.md - Advanced patterns (validation, events, plugins, Next.js)
  • references/step-config.md - 完整的步骤配置选项
  • references/hooks-api.md - 完整的useOnboarding API
  • references/patterns.md - 进阶模式(验证、事件、插件、Next.js)