onboardjs-react
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseOnboardJS 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 with React dependencies. If not a React project, inform the user.
package.json2. Detect package manager - Check for lock files to determine the correct install command:
| Lock File | Package Manager | Install Command |
|---|---|---|
| pnpm | |
| yarn | |
| bun | |
| npm | |
3. Detect Next.js - Check for , , or in dependencies. If Next.js, see Next.js Setup section.
next.config.jsnext.config.mjsnext1. 验证React项目 - 检查是否存在包含React依赖的。如果不是React项目,请告知用户。
package.json2. 检测包管理器 - 检查锁文件以确定正确的安装命令:
| 锁文件 | 包管理器 | 安装命令 |
|---|---|---|
| pnpm | |
| yarn | |
| bun | |
| npm | |
Installation
安装
bash
undefinedbash
undefinednpm
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
undefinedbun add @onboardjs/core @onboardjs/react
undefinedQuick 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 object does NOT have a array. Use and , or calculate from step IDs.
statestepscurrentStepNumbertotalSteps重要提示: 对象没有数组。请使用和,或通过步骤ID计算。
statestepscurrentStepNumbertotalStepsOption 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)