Callbacks
Tourista provides callback functions that allow you to hook into tour lifecycle events and track user interactions. These callbacks are useful for analytics, logging, and triggering side effects based on tour progress.
Available Callbacks
The TourMachine component accepts the following callback props:
onStart
Triggered when a tour begins.
<TourMachine
onStart={(tourId) => {
console.log(`Tour ${tourId} started`);
}}
/>Parameters:
tourId: string- The ID of the tour that started
Use cases:
Track tour engagement in analytics
Initialize tour-specific resources
Update UI state
onStepChange
Called whenever the tour progresses to a different step.
<TourMachine
onStepChange={(stepIndex, stepId, tourId) => {
console.log(`Tour ${tourId}: Step ${stepIndex} (${stepId})`);
}}
/>Parameters:
stepIndex: number- The index of the current step (0-based)stepId: string- The ID of the current steptourId: string- The ID of the active tour
Important notes:
Only triggered when the step index actually changes
Not called for async sub-state transitions (pending → processing → success)
Not called during navigation states
onComplete
Fired when a tour completes successfully.
<TourMachine
onComplete={(tourId) => {
console.log(`Tour ${tourId} completed`);
}}
/>Parameters:
tourId: string- The ID of the completed tour
Use cases:
Mark user as onboarded
Unlock features
Send completion analytics
onSkip
Called when a user skips or exits the tour.
<TourMachine
onSkip={(stepIndex, stepId, tourId) => {
console.log(`Tour ${tourId} skipped at step ${stepIndex} (${stepId})`);
}}
/>Parameters:
stepIndex: number- The step index where the tour was skippedstepId: string- The ID of the step where skipping occurredtourId: string- The ID of the skipped tour
Triggers:
User clicks skip button
User presses Escape key
User clicks outside the tour (if
closeOnClickOutsideis true)Browser back button is pressed
Complete Example
Here's how to use all callbacks together:
// components/TourProvider.tsx
'use client';
import { TourProvider as TourProviderComponent, TourMachine } from 'tourista';
export function TourProvider({ children }: { children: React.ReactNode }) {
return (
<TourProviderComponent tours={tours}>
<TourMachine
onStart={(tourId) => {
// Track tour start
analytics.track('Tour Started', {
tourId,
timestamp: new Date().toISOString(),
});
}}
onStepChange={(stepIndex, stepId, tourId) => {
// Track progress
analytics.track('Tour Step Changed', {
tourId,
stepIndex,
stepId,
progress: `${stepIndex + 1}/${totalSteps}`,
});
// Update progress bar
setProgress(((stepIndex + 1) / totalSteps) * 100);
}}
onComplete={(tourId) => {
// Mark as complete
localStorage.setItem(`tour_${tourId}_completed`, 'true');
// Track completion
analytics.track('Tour Completed', {
tourId,
completedAt: new Date().toISOString(),
});
// Show success message
toast.success('Tour completed! 🎉');
}}
onSkip={(stepIndex, stepId, tourId) => {
// Track skip event
analytics.track('Tour Skipped', {
tourId,
skippedAt: stepIndex,
stepId,
});
// Offer to resume
if (stepIndex > 0) {
showResumePrompt(tourId, stepIndex);
}
}}
/>
{children}
</TourProviderComponent>
);
}
// app/layout.tsx
import { TourProvider } from '@/components/TourProvider';
function App() {
return <TourProvider>{/* Your app content */}</TourProvider>;
}Analytics Integration
A common pattern is to integrate callbacks with your analytics service:
const tourCallbacks = {
onStart: (tourId: string) => {
// Google Analytics
gtag('event', 'tour_start', {
tour_id: tourId,
});
// Mixpanel
mixpanel.track('Tour Started', { tourId });
// Amplitude
amplitude.track('Tour Started', { tourId });
},
onStepChange: (stepIndex: number, stepId: string, tourId: string) => {
// Track each step
gtag('event', 'tour_progress', {
tour_id: tourId,
step_index: stepIndex,
step_id: stepId,
});
},
onComplete: (tourId: string) => {
// Track completion
gtag('event', 'tour_complete', {
tour_id: tourId,
});
// Set user property
mixpanel.people.set({
[`completed_${tourId}`]: true,
});
},
onSkip: (stepIndex: number, stepId: string, tourId: string) => {
// Track abandonment
gtag('event', 'tour_skip', {
tour_id: tourId,
abandon_step: stepIndex,
abandon_step_id: stepId,
});
},
};
// Use the callbacks in your TourProvider component
export function TourProvider({ children }: { children: React.ReactNode }) {
return (
<TourProviderComponent tours={tours}>
<TourMachine {...tourCallbacks} />
{children}
</TourProviderComponent>
);
}Conditional Logic
Use callbacks to implement conditional behavior:
// components/TourProvider.tsx
'use client';
import { TourProvider as TourProviderComponent, TourMachine } from 'tourista';
export function TourProvider({ children }: { children: React.ReactNode }) {
return (
<TourProviderComponent tours={tours}>
<TourMachine
onStepChange={(stepIndex, stepId, tourId) => {
// Unlock features as user progresses
if (stepId === 'dashboard-intro') {
unlockDashboard();
}
if (stepId === 'advanced-features') {
enableAdvancedMode();
}
}}
onComplete={(tourId) => {
// Different actions for different tours
switch (tourId) {
case 'onboarding':
markUserAsOnboarded();
redirectToDashboard();
break;
case 'new-feature':
enableFeature();
showSuccessNotification();
break;
}
}}
/>
{children}
</TourProviderComponent>
);
}Debugging
Use callbacks for debugging during development:
// components/TourProvider.tsx
'use client';
import { TourProvider as TourProviderComponent, TourMachine } from 'tourista';
const debugCallbacks =
process.env.NODE_ENV === 'development'
? {
onStart: (tourId) => {
console.group(`🚀 Tour Started: ${tourId}`);
console.log('Timestamp:', new Date().toISOString());
console.groupEnd();
},
onStepChange: (stepIndex, stepId, tourId) => {
console.log(`📍 Step ${stepIndex}: ${stepId} (Tour: ${tourId})`);
},
onComplete: (tourId) => {
console.log(`✅ Tour Completed: ${tourId}`);
},
onSkip: (stepIndex, stepId, tourId) => {
console.warn(`⏭️ Tour Skipped at step ${stepIndex} (${stepId})`);
},
}
: {};
export function TourProvider({ children }: { children: React.ReactNode }) {
return (
<TourProviderComponent tours={tours}>
<TourMachine {...debugCallbacks} />
{children}
</TourProviderComponent>
);
}Important Notes
Callback execution order: Callbacks are called after the state change has occurred
Performance: Keep callbacks lightweight to avoid impacting tour performance
Step changes only:
onStepChangeonly fires when moving between different steps, not for async sub-states
Last updated