⚠️ NOTE ⚠️: The
next-wizardis still experimental and can be imported at the path'@krakentech/blueprint-onboarding/next-wizard'.
API reference
The Next Wizard is a set of utils which handle server side state persistence, submission validation and routing for journeys which span across multiple pages. Using the builder pattern, extensive type safety is provided. In contrast to the previous wizard, this one is specifically designed for NextJS SSR usage. By simply providing a set of utils, the usage is very flexible and can be adjusted to meet each projects needs.
The createNextWizard() builder function
There are multiple configuration options in the createNextWizard builder function of the wizard, which are explained in the following.
export const wizard = createNextWizard<
TStepNames extends Record<string, string>,
TState extends Record<string, unknown>,
TSerializedState extends Record<string, unknown>,
>({
cookieName,
encryptionSecret,
cookieApiHandlerRoute,
initialState,
steps,
serializeState,
deserializeState,
cookieOptions,
});
cookieName
The name of the cookie that stores the state. Ensure that this name is unique to avoid conflicts with other cookies.
encryptionSecret (optional)
The secret used to encrypt the cookie state. If no secret is provided then the state will not be encrypted.
NOTE: If the secret is changed then the getCookieStateOnServer function will delete the cookie and return default values for the cookie data.
cookieApiHandlerRoute
To be able to persist the state in the secure httpOnly cookie an API route has to be setup with the getCookieStateApiHandler util. And this cookieApiHandlerRoute config option provides the wizard with the path to this internal endpoint.
initialState
The initial state which is also used as a fallback value if there is an issue with the retrieval of existing state.
steps
The steps config is used to control the routing between the steps. Each step requires a name as identifier and pathname for navigation. The configuration can be defined as a static array, or as a dynamic function which is dependant on the state.
// Static Steps Config
const staticStepsConfig = [
{
name: StepName.Supply,
pathname: '/onboarding/1-supply',
},
{
name: StepName.TariffSelection,
pathname: '/onboarding/2-tariff-selection',
},
...
] as const satisfies WizardSteps<StepName>;
This example shows a config where the steps are based on a tariff choice.
const getStepsConfig: WizardStepsConfig<TStepName, TState> = (state) => {
if (state.formValues.tariffName === 'octo_basic') {
return stepsConfigA;
}
return stepsConfigB;
};
query
Steps can optionally include a query field that can either be static using a constant definition, or dynamic using a function definition.
// static query params
const steps = [
{
name: StepName.EditPersonalDetails,
pathname: '/onboarding/3-personal-details',
query: {
edit: 'true',
},
...
}
] as const satisfies WizardSteps<StepName>;
// dynamic query params
const getSteps = (state: TState) => [
{
name: StepName.Supply,
pathname: '/onboarding/1-supply',
query: {
affiliate: state.formValues.affiliateCode,
},
...
}
] as const satisfies WizardSteps<StepName>;
serializeState / deserializeState
The wizard requires a serializeState function which translates the state object into a format that can be stored (cookie) or transmitted (from the server to the client) and reconstructed with the deserializeState function later. Please ensure that there are no undefined values, since these are not serializable and will cause errors when returned by the getServerSideProps function. Instead, use null to represent empty states.
The following example shows how to (de-)serialize date values.
import { parseISO } from 'date-fns';
type FormValues = {
// ...
birthday: Date | null;
};
type SerializedFormValues = Omit<FormValues, 'birthday'> & {
birthday: string | null;
};
type WizardState = {
formValues: FormValues;
};
// The serialized state is also typed to provide full type safety
type WizardSerializedState = {
formValues: SerializedFormValues;
};
const serializeState = (state: WizardState): WizardSerializedState => {
return {
...state,
formValues: {
...state.formValues,
[OnboardingKey.DateOfBirth]:
state.formValues[OnboardingKey.DateOfBirth]?.toISOString() ??
null,
},
};
};
const deserializeState = (
serializedState: WizardSerializedState
): WizardState => {
return {
...serializedState,
formValues: {
...serializedState.formValues,
[OnboardingKey.DateOfBirth]: serializedState.formValues[
OnboardingKey.DateOfBirth
]
? parseISO(
serializedState.formValues[OnboardingKey.DateOfBirth]
)
: null,
},
};
};
If the state doesn't require serialization, e.g., if only simple data types are used in the form, then simply return the values back directly. These functions were intentionally made non-optional, to enforce consistent patterns and keep the typing simple.
type State = { ... };
type SerializedState = State;
const serializeState = (state: State): SerializedState => state;
const deserializeState = (serializedState: SerializedState): State => serializedState;
Utils
These utils are returned by the createNextWizard builder function.
getInitialState
Gets a structured clone of the initial state of the wizard.
getInitialState: () => TState;
getCookieStateOnServer
Gets the cookie state and deserializes it.
It is meant to be used only in the getServerSideProps function.
getCookieStateOnServer: (context: GetServerSidePropsContext) => Promise<TState>;
setCookieStateOnClient
Set the cookie state from the client side.
This is typically used in the onSubmit handler of a form.
setCookieStateOnClient: (options: {
state: TState;
fetchOptions?: Pick<RequestInit, 'signal'>;
}) => Promise<void>;
setCookieStateOnServer
Set the cookie state from the server side (getServerSideProps).
setCookieStateOnServer: (options: {
state: TState;
context: GetServerSidePropsContext;
}) => Promise<void>;
deleteCookieOnServer
Delete the cookie state from the server side (getServerSideProps).
IMPORTANT! When deleting a cookie and you're not relying on the default attributes, you must pass the exact same path and domain attributes that were used to set the cookie.
deleteCookieOnServer: (options: {
context: GetServerSidePropsContext;
cookieOptions?: Pick<WizardCookieOptions, 'path' | 'domain'>;
}) => Promise<void>;
getNextStep
Gets the next step based on the current step name and state.
getNextStep: (options: {
currentStepName: StepName<TStepNames>;
state: TState;
}) => WizardStep<StepName<TStepNames>>;
toNextStep
Navigates to the next step using the provided NextRouter.
toNextStep: (options: {
currentStepName: StepName<TStepNames>;
state: TState;
router: NextRouter;
}) => Promise<boolean>;
getPreviousStep
Gets the previous step based on the current step name and state.
getPreviousStep: (options: {
currentStepName: StepName<TStepNames>;
state: TState;
}) => WizardStep<StepName<TStepNames>>;
toPreviousStep
Navigates to the previous step using the provided NextRouter.
toPreviousStep: (options: {
currentStepName: StepName<TStepNames>;
state: TState;
router: NextRouter;
}) => Promise<boolean>;
getStep
Gets a specific step by its name and state.
getStep: (options: { stepName: StepName<TStepNames>; state: TState }) =>
WizardStep<StepName<TStepNames>>;
toStep
Navigates to a specific step using the provided NextRouter.
toStep: (options: {
stepName: StepName<TStepNames>;
state: TState;
router: NextRouter;
}) => Promise<boolean>;
getSteps
Gets the currently active steps from the steps config.
If the steps are dynamic (a function), it evaluates them based on the current state.
getSteps: (state: TState) => ReadonlyArray<WizardStep<StepName<TStepNames>>>;
serializeState
Serializes the state into a storable format (e.g., for cookies).
serializeState: (state: TState) => TSerializedState;
deserializeState
Deserializes the state after retrieving it from the cookie.
deserializeState: (serializedState: TSerializedState) => TState;
getStepDestination
Gets the destination URL for a given step.
getStepDestination: (step: WizardStep<StepName<TStepNames>>) => string;
getRedirectStep
Gets the redirect step for verification or validation problems.
This is typically the first step that hasn't been submitted yet.
getRedirectStep: (options: {
state: TState;
submittedSteps: StepName<TStepNames>[];
}) => WizardStep<StepName<TStepNames>>;
getRedirectDestination
Gets the redirect destination URL for verification or validation problems.
getRedirectDestination: (options: {
state: TState;
submittedSteps: StepName<TStepNames>[];
}) => string;
getRedirect
Returns a redirect object for use in getServerSideProps.
This is a convenience utility for handling redirects in Next.js.
getRedirect: (options: {
state: TState;
submittedSteps: StepName<TStepNames>[];
}) => {
redirect: Redirect;
};
getIsStepUnlocked
Verifies if the given step is already unlocked.
A step is unlocked if all previous steps have been submitted.
This is meant to be used in getServerSideProps.
getIsStepUnlocked: (options: {
stepName: StepName<TStepNames>;
submittedSteps: StepName<TStepNames>[];
state: TState;
}) => boolean;
addToSubmittedSteps
Adds a step to the list of submitted steps.
Typically used in the onSubmit function to track progress.
addToSubmittedSteps: (options: {
stepName: StepName<TStepNames>;
submittedSteps: StepName<TStepNames>[];
}) => StepName < TStepNames > [];
handleQueryParams
Streamlined approach to inject, persist and remove initial query parameters.
It is meant to be used in getServerSideProps of the entry step.
The injection function can either mutate the state or return a new state.
handleQueryParams: (options: {
state: TState;
stepName: StepName<TStepNames>;
injectQueryParamsFunc: InjectQueryParamsFunc<TState>;
context: GetServerSidePropsContext;
}) => Promise<{ redirect: Redirect } | null>;
The injectQueryParamsFunc receives the context and state and can either modify the state directly or return a new state:
type InjectQueryParamsFunc<TState> = (options: {
context: GetServerSidePropsContext;
state: TState;
}) => TState | void | Promise<TState | void>;
When query parameters are present, this function will:
- Process them using your
injectQueryParamsFunc - Persist the updated state to the cookie
- Return a redirect to the same page, thereby removing any unwanted query parameters
If there are no query parameters or if the redirect destination is the same as the current URL, it will return null.