useWizardContext
Hook for accessing wizard context within wizard components
useWizardContext Hook
The useWizardContext hook provides access to the wizard context API from within any component that's rendered inside a wizard. This is essential for creating custom navigation components, step indicators, and accessing cross-step data.
Usage
import { useWizardContext } from 'graneet-form';
const wizardContext = useWizardContext<WizardValuesType>();Parameters
- Generic type parameter
WizardValues extends Record<string, FieldValues>- The type defining all wizard step data
Returns
Returns the WizardContextApi<WizardValues> object:
Prop
Type
Examples
Custom Wizard Navigation
interface WizardData {
userInfo: { firstName: string; lastName: string; email: string; };
preferences: { theme: 'light' | 'dark'; notifications: boolean; };
confirmation: { agreed: boolean; };
}
function WizardNavigation() {
const {
steps,
currentStep,
goNext,
goPrevious,
goBackTo,
isFirstStep,
isLastStep,
isStepReady
} = useWizardContext<WizardData>();
return (
<div className="wizard-navigation">
<div className="steps-indicator">
{steps.map((stepName, index) => {
const currentIndex = steps.indexOf(currentStep);
const isCompleted = index < currentIndex;
const isCurrent = index === currentIndex;
const isAccessible = index <= currentIndex;
return (
<button
key={stepName}
onClick={() => isAccessible && goBackTo(stepName)}
disabled={!isAccessible}
className={`step ${isCompleted ? 'completed' : ''} ${isCurrent ? 'current' : ''}`}
>
<span className="step-number">{index + 1}</span>
<span className="step-name">{stepName}</span>
{isCompleted && <span className="checkmark">✓</span>}
</button>
);
})}
</div>
<div className="navigation-buttons">
<button
onClick={goPrevious}
disabled={isFirstStep}
className="btn-secondary"
>
{isFirstStep ? 'Cancel' : 'Previous'}
</button>
<button
onClick={goNext}
disabled={!isStepReady}
className={isStepReady ? 'btn-primary' : 'btn-disabled'}
>
{isLastStep ? 'Complete' : 'Next'}
</button>
</div>
</div>
);
}Wizard Progress Bar
function WizardProgressBar() {
const { steps, currentStep } = useWizardContext<WizardData>();
const currentStepIndex = steps.indexOf(currentStep);
const progress = ((currentStepIndex + 1) / steps.length) * 100;
return (
<div className="wizard-progress">
<div className="progress-bar">
<div
className="progress-fill"
style={{ width: `${progress}%` }}
/>
</div>
<div className="progress-text">
Step {currentStepIndex + 1} of {steps.length}: {currentStep}
</div>
</div>
);
}Cross-Step Data Display
function WizardSummary() {
const { getValuesOfStep, getValuesOfSteps } = useWizardContext<WizardData>();
// Get data from specific steps
const userInfo = getValuesOfStep('userInfo');
const preferences = getValuesOfStep('preferences');
// Get all wizard data
const allData = getValuesOfSteps();
return (
<div className="wizard-summary">
<h2>Registration Summary</h2>
{userInfo && (
<div className="summary-section">
<h3>Personal Information</h3>
<p>Name: {userInfo.firstName} {userInfo.lastName}</p>
<p>Email: {userInfo.email}</p>
</div>
)}
{preferences && (
<div className="summary-section">
<h3>Preferences</h3>
<p>Theme: {preferences.theme}</p>
<p>Notifications: {preferences.notifications ? 'Enabled' : 'Disabled'}</p>
</div>
)}
<details>
<summary>View Raw Data</summary>
<pre>{JSON.stringify(allData, null, 2)}</pre>
</details>
</div>
);
}Conditional Step Content
function StepContent() {
const { currentStep, getValuesOfStep } = useWizardContext<WizardData>();
const userInfo = getValuesOfStep('userInfo');
const renderStep = () => {
switch (currentStep) {
case 'userInfo':
return <UserInfoStep />;
case 'preferences':
return <PreferencesStep />;
case 'confirmation':
// Show different content based on previous step data
if (userInfo?.email?.includes('@company.com')) {
return <BusinessConfirmationStep />;
}
return <PersonalConfirmationStep />;
default:
return <div>Unknown step: {currentStep}</div>;
}
};
return (
<div className="step-content">
{renderStep()}
</div>
);
}Wizard Breadcrumbs
function WizardBreadcrumbs() {
const { steps, currentStep, goBackTo } = useWizardContext<WizardData>();
const currentStepIndex = steps.indexOf(currentStep);
return (
<nav className="wizard-breadcrumbs">
{steps.map((stepName, index) => {
const isAccessible = index <= currentStepIndex;
const isCurrent = stepName === currentStep;
return (
<span key={stepName} className="breadcrumb-item">
{index > 0 && <span className="separator"> › </span>}
{isAccessible ? (
<button
onClick={() => goBackTo(stepName)}
className={`breadcrumb-link ${isCurrent ? 'current' : ''}`}
>
{stepName}
</button>
) : (
<span className="breadcrumb-disabled">{stepName}</span>
)}
</span>
);
})}
</nav>
);
}Step Validation Status
function StepValidationStatus() {
const { currentStep } = useWizardContext<WizardData>();
const stepStatus = useStepStatus();
const getStatusMessage = () => {
switch (stepStatus) {
case 'valid':
return `✓ ${currentStep} step completed successfully`;
case 'invalid':
return `⚠ Please fix errors in ${currentStep} step`;
case 'pending':
return `⏳ Validating ${currentStep} step...`;
case 'undetermined':
return `📝 Complete ${currentStep} step to continue`;
default:
return `Unknown status for ${currentStep} step`;
}
};
return (
<div className={`step-status status-${stepStatus}`}>
{getStatusMessage()}
</div>
);
}Wizard Data Export
function WizardDataExport() {
const { getValuesOfSteps, currentStep } = useWizardContext<WizardData>();
const exportData = () => {
const allData = getValuesOfSteps();
const dataStr = JSON.stringify(allData, null, 2);
const dataBlob = new Blob([dataStr], { type: 'application/json' });
const url = URL.createObjectURL(dataBlob);
const link = document.createElement('a');
link.href = url;
link.download = 'wizard-data.json';
link.click();
URL.revokeObjectURL(url);
};
const saveProgress = () => {
const allData = getValuesOfSteps();
localStorage.setItem('wizardProgress', JSON.stringify({
currentStep,
data: allData,
timestamp: new Date().toISOString()
}));
};
return (
<div className="wizard-actions">
<button onClick={saveProgress} className="btn-secondary">
Save Progress
</button>
<button onClick={exportData} className="btn-secondary">
Export Data
</button>
</div>
);
}Step Timer Component
function StepTimer() {
const { currentStep } = useWizardContext<WizardData>();
const [startTime] = useState(Date.now());
const [elapsed, setElapsed] = useState(0);
useEffect(() => {
const timer = setInterval(() => {
setElapsed(Date.now() - startTime);
}, 1000);
return () => clearInterval(timer);
}, [startTime]);
const formatTime = (ms: number) => {
const seconds = Math.floor(ms / 1000);
const minutes = Math.floor(seconds / 60);
return `${minutes}:${(seconds % 60).toString().padStart(2, '0')}`;
};
return (
<div className="step-timer">
Time on {currentStep}: {formatTime(elapsed)}
</div>
);
}Wizard Layout Component
function WizardLayout({ children }: { children: React.ReactNode }) {
const { currentStep, isFirstStep, isLastStep } = useWizardContext<WizardData>();
return (
<div className="wizard-layout">
<header className="wizard-header">
<h1>Registration Wizard</h1>
<WizardProgressBar />
</header>
<aside className="wizard-sidebar">
<WizardBreadcrumbs />
<StepValidationStatus />
</aside>
<main className="wizard-main">
<div className="step-header">
<h2>{currentStep}</h2>
<StepTimer />
</div>
<div className="step-content">
{children}
</div>
</main>
<footer className="wizard-footer">
<WizardNavigation />
{!isFirstStep && !isLastStep && <WizardDataExport />}
</footer>
</div>
);
}Context Requirements
The useWizardContext hook must be used within a component tree that has a wizard context provider:
// ✅ Correct usage
function MyWizard() {
const wizard = useWizard(steps, onFinish);
return (
<WizardContext.Provider value={wizard}>
<WizardComponent /> {/* Can use useWizardContext */}
</WizardContext.Provider>
);
}
// ❌ Incorrect usage - will throw error
function WizardComponent() {
const wizard = useWizardContext<WizardData>(); // Error: No wizard context found
// ...
}Performance Notes
- The hook provides direct access to the wizard context with minimal overhead
- Navigation methods are memoized to prevent unnecessary re-renders
- Data retrieval methods are optimized for frequent access
- Context updates only trigger re-renders when relevant state changes