1"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[2360],{854:(e,s,n)=>{n.r(s),n.d(s,{assets:()=>o,contentTitle:()=>d,default:()=>h,frontMatter:()=>a,metadata:()=>r,toc:()=>l});const r=JSON.parse('{"id":"guides/onboarding/use-wizard-api","title":"use-wizard-api","description":"Reference for the various features of the wizard","source":"@site/docs/guides/onboarding/use-wizard-api.mdx","sourceDirName":"guides/onboarding","slug":"/guides/onboarding/use-wizard-api","permalink":"/docs/guides/onboarding/use-wizard-api","draft":false,"unlisted":false,"tags":[],"version":"current","sidebarPosition":3,"frontMatter":{"sidebar_position":3,"sidebar_label":"API reference","description":"Reference for the various features of the wizard"},"sidebar":"docsSidebar","previous":{"title":"API reference","permalink":"/docs/guides/onboarding/next-wizard-pages-router-api"},"next":{"title":"Code examples","permalink":"/docs/guides/onboarding/use-wizard-examples"}}');var t=n(5270),i=n(5907);const a={sidebar_position:3,sidebar_label:"API reference",description:"Reference for the various features of the wizard"},d="API reference",o={},l=[{value:"The <em><code>createWizard()</code></em> builder function",id:"the-createwizard-builder-function",level:2},{value:"<em><code>storageKey</code></em>",id:"storagekey",level:3},{value:"<em><code>stepNames</code></em>",id:"stepnames",level:3},{value:"<em><code>defaultFormValues</code></em>",id:"defaultformvalues",level:3},{value:"<em><code>storage</code></em>",id:"storage",level:3},{value:"<em><code>serializeFormValues</code></em> / <em><code>parseFormValues</code></em>",id:"serializeformvalues--parseformvalues",level:3},{value:"<em><code>useWizard</code></em>",id:"usewizard",level:2},{value:"What's inside",id:"whats-inside",level:3},{value:"<code>stepperProps</code>",id:"stepperprops",level:4},{value:"<em><code>WizardProvider</code></em>",id:"wizardprovider",level:2},{value:"props",id:"props",level:3},{value:"steps",id:"steps",level:3},{value:"Step hierarchy",id:"step-hierarchy",level:4},{value:"<code>WizardStep<TStepName></code>",id:"wizardsteptstepname",level:4},{value:"queryParams",id:"queryparams",level:4},{value:"<em><code>WizardFormHydrator</code></em>",id:"wizardformhydrator",level:2},{value:"<em><code>WizardRouteGuard</code></em>",id:"wizardrouteguard",level:2},{value:"<em><code>validateStep</code></em>",id:"validatestep",level:3},{value:"<em><code>onInvalidStep</code></em>",id:"oninvalidstep",level:3}];function c(e){const s={a:"a",blockquote:"blockquote",code:"code",em:"em",h1:"h1",h2:"h2",h3:"h3",h4:"h4",header:"header",p:"p",pre:"pre",strong:"strong",table:"table",tbody:"tbody",td:"td",th:"th",thead:"thead",tr:"tr",...(0,i.R)(),...e.components};return(0,t.jsxs)(t.Fragment,{children:[(0,t.jsxs)(s.blockquote,{children:["\n",(0,t.jsxs)(s.p,{children:[(0,t.jsx)(s.strong,{children:"\u26a0\ufe0f NOTE \u26a0\ufe0f"}),": This wizard is deprecated in favor of the ",(0,t.jsx)(s.code,{children:"next-wizard"})," which can be imported at the path '@krakentech/blueprint-onboarding/next-wizard'."]}),"\n"]}),"\n",(0,t.jsx)(s.header,{children:(0,t.jsx)(s.h1,{id:"api-reference",children:"API reference"})}),"\n",(0,t.jsx)(s.p,{children:"The Wizard handles state persistence, form hydration and routing logic for journeys which require multiple steps. Using the builder pattern, extensive type safety is provided."}),"\n",(0,t.jsxs)(s.h2,{id:"the-createwizard-builder-function",children:["The ",(0,t.jsx)(s.em,{children:(0,t.jsx)(s.code,{children:"createWizard()"})})," builder function"]}),"\n",(0,t.jsxs)(s.p,{children:["There are multiple configuration options which can be configured in the ",(0,t.jsx)(s.code,{children:"createWizard"})," builder function of the wizard, which are explained in the following. Subsequently, the returned values ",(0,t.jsx)(s.code,{children:"useWizard"}),", ",(0,t.jsx)(s.code,{children:"WizardProvider"}),", ",(0,t.jsx)(s.code,{children:"HydrateForm"}),", ",(0,t.jsx)(s.code,{children:"WizardRouteGuard"}),", ",(0,t.jsx)(s.code,{children:"wizardRouteGuardHelpers"})," and their configuration is shown."]}),"\n",(0,t.jsx)(s.pre,{children:(0,t.jsx)(s.code,{className:"language-tsx",children:"export const {\n useWizard,\n WizardProvider,\n WizardFormHydrator,\n WizardRouteGuard,\n wizardRouteGuardHelpers,\n} = createWizard({\n storageKey,\n stepNames,\n defaultFormValues,\n storage,\n serializeFormValues,\n parseFormValues,\n});\n"})}),"\n",(0,t.jsx)(s.h3,{id:"storagekey",children:(0,t.jsx)(s.em,{children:(0,t.jsx)(s.code,{children:"storageKey"})})}),"\n",(0,t.jsx)(s.p,{children:"The storage key is used to persist the form values in the session storage. Therefore, each wizard instance requires a unique key to prevent storage collisions."}),"\n",(0,t.jsx)(s.h3,{id:"stepnames",children:(0,t.jsx)(s.em,{children:(0,t.jsx)(s.code,{children:"stepNames"})})}),"\n",(0,t.jsxs)(s.p,{children:["The ",(0,t.jsx)(s.code,{children:"stepNames"})," are used for navigation and the step configuration. It can be provided as an enum."]}),"\n",(0,t.jsx)(s.pre,{children:(0,t.jsx)(s.code,{className:"language-tsx",children:"enum StepName {\n Supply = 'supply',\n TariffSelection = 'tariff-selection',\n DetailsPersonal = 'details-pers
1onal',\n}\n"})}),"\n",(0,t.jsx)(s.p,{children:"or a const object"}),"\n",(0,t.jsx)(s.pre,{children:(0,t.jsx)(s.code,{className:"language-tsx",children:"const StepName {\n Supply: 'supply',\n TariffSelection: 'tariff-selection',\n DetailsPersonal: 'details-personal',\n} as const\n"})}),"\n",(0,t.jsx)(s.h3,{id:"defaultformvalues",children:(0,t.jsx)(s.em,{children:(0,t.jsx)(s.code,{children:"defaultFormValues"})})}),"\n",(0,t.jsxs)(s.p,{children:["The default form values are used to initialise the form values state. They will also be used as placeholder values until the session storage has been read. The wizard will infer the form values type from the ",(0,t.jsx)(s.code,{children:"defaultFormValues"})," object, if no type is passed into the respective generic field, i.e., the first positional argument. Please note that the values object should be flat and contain all step values."]}),"\n",(0,t.jsx)(s.pre,{children:(0,t.jsx)(s.code,{className:"language-tsx",children:"export enum FieldName {\n StreetName = 'streetName', // Step 1\n HouseNumber = 'houseNumber', // Step 1\n TariffName = 'tariffName', // Step 2\n FirstName = 'firstName', // Step 3\n LastName = 'lastName', // Step 3\n Birthday = 'birthday', // Step 3\n}\n\nexport type FormValues = {\n [FieldName.StreetName]: string;\n [FieldName.HouseNumber]: string;\n [FieldName.TariffName]: string;\n [FieldName.FirstName]: string;\n [FieldName.LastName]: string;\n [FieldName.Birthday]: Date | null;\n};\n\nconst defaultFormValues: FormValues = {\n [FieldName.StreetName]: '',\n [FieldName.HouseNumber]: '',\n [FieldName.TariffName]: '',\n [FieldName.FirstName]: '',\n [FieldName.LastName]: '',\n [FieldName.Birthday]: null;\n};\n"})}),"\n",(0,t.jsx)(s.h3,{id:"storage",children:(0,t.jsx)(s.em,{children:(0,t.jsx)(s.code,{children:"storage"})})}),"\n",(0,t.jsxs)(s.p,{children:["If a custom storage is needed, then read and write functions can be configured.\nHere is an example to save values in local storage rather than the default session storage, which can be useful when ",(0,t.jsx)(s.a,{href:"/docs/guides/onboarding/use-wizard-examples#storing-values-in-local-storage-to-resume-a-journey",children:"resuming an unfinished journey"}),"."]}),"\n",(0,t.jsx)(s.pre,{children:(0,t.jsx)(s.code,{className:"language-tsx",children:"const storage: StorageConfig = {\n read: (storageKey) => {\n const json = localStorage.getItem(storageKey);\n if (json === null) {\n return null;\n }\n return JSON.parse(json);\n },\n write: (storageKey, values) => {\n localStorage.setItem(storageKey, JSON.stringify(values));\n },\n};\n"})}),"\n",(0,t.jsxs)(s.h3,{id:"serializeformvalues--parseformvalues",children:[(0,t.jsx)(s.em,{children:(0,t.jsx)(s.code,{children:"serializeFormValues"})})," / ",(0,t.jsx)(s.em,{children:(0,t.jsx)(s.code,{children:"parseFormValues"})})]}),"\n",(0,t.jsxs)(s.p,{children:["The form values are stringified with ",(0,t.jsx)(s.code,{children:"JSON.stringify(...)"})," when persisted to storage, and parsed with ",(0,t.jsx)(s.code,{children:"JSON.parse()"})," when retrieved from storage. For non-trivial datatypes it may be necessary to add custom serializer and parser functionality. For example a ",(0,t.jsx)(s.code,{children:"Date"})," object is automatically transformed to an ISO string by the ",(0,t.jsx)(s.code,{children:"JSON.stringify(...)"})," function. However, the ",(0,t.jsx)(s.code,{children:"JSON.parse()"})," function does not transform ISO date strings back to the ",(0,t.jsx)(s.code,{children:"Date"})," type, which causes the form value to switch from ",(0,t.jsx)(s.code,{children:"Date"})," to ",(0,t.jsx)(s.code,{children:"string"})," type. The following example shows how the ",(0,t.jsx)(s.code,{children:"serializeFormValues"})," / ",(0,t.jsx)(s.code,{children:"parseFormValues"})," functions can be used for this case. Since ",(0,t.jsx)(s.code,{children:"JSON.stringify(...)"})," serializes ",(0,t.jsx)(s.code,{children:"Date"})," to an ISO string by default, the ",(0,t.jsx)(s.code,{children:"serializeFormValues"})," function is technically unnecessary for this particular example. It is still recommended to provide a function for both directions to avoid confusion and bugs."]}),"\n",(0,t.jsx)(s.pre,{children:(0,t.jsx)(s.code,{children:"import { parseISO } from 'date-fns';\n\ntype SerializedFormValues = Omit<FormValues, 'birthday'> & {\n birthday: string | null;\n};\n\nconst parseFormValues = (\n values: SerializedFormValues\n): FormValues =>
1 {\n return {\n ...values,\n birthday: values.birthday ? parseISO(values.birthday) : null,\n };\n};\n\nconst serializeFormValues = (\n values: FormValues\n): SerializedFormValues => {\n return { ...values, birthday: values.birthday?.toISOString() ?? null };\n};\n"})}),"\n",(0,t.jsx)(s.h2,{id:"usewizard",children:(0,t.jsx)(s.em,{children:(0,t.jsx)(s.code,{children:"useWizard"})})}),"\n",(0,t.jsx)(s.p,{children:"This hook gives you access the wizard context. This encompasses things like routing and state management functions, as in this example."}),"\n",(0,t.jsx)(s.pre,{children:(0,t.jsx)(s.code,{className:"language-tsx",children:"const SomeStep = () => {\n const { formValues, setFormValues, toNextStep, toPreviousStep } =\n useWizard();\n\n return (\n <Formik\n initialValues={formValues}\n onSubmit={async (values) => {\n setFormValues(values);\n await toNextStep();\n }}\n >\n <Form>...</Form>\n <Button onClick={() => toPreviousStep()}>Back</Button>\n </Formik>\n );\n};\n"})}),"\n",(0,t.jsx)(s.h3,{id:"whats-inside",children:"What's inside"}),"\n",(0,t.jsxs)(s.p,{children:["These are the key things returned from the ",(0,t.jsx)(s.code,{children:"useWizard"})," hook:"]}),"\n",(0,t.jsxs)(s.table,{children:[(0,t.jsx)(s.thead,{children:(0,t.jsxs)(s.tr,{children:[(0,t.jsx)(s.th,{children:"Name"}),(0,t.jsx)(s.th,{children:"Description"}),(0,t.jsx)(s.th,{children:"Type"})]})}),(0,t.jsxs)(s.tbody,{children:[(0,t.jsxs)(s.tr,{children:[(0,t.jsx)(s.td,{children:(0,t.jsx)(s.code,{children:"formValues"})}),(0,t.jsxs)(s.td,{children:["Values from the form fields stored in context, the defaults and stypes of which are defined by what is passed into ",(0,t.jsx)(s.code,{children:"createWizard"})]}),(0,t.jsx)(s.td,{children:(0,t.jsx)(s.code,{children:"TFormValues"})})]}),(0,t.jsxs)(s.tr,{children:[(0,t.jsx)(s.td,{children:(0,t.jsx)(s.code,{children:"defaultFormValues"})}),(0,t.jsx)(s.td,{children:"The default step form values"}),(0,t.jsx)(s.td,{children:(0,t.jsx)(s.code,{children:"TFormValues"})})]}),(0,t.jsxs)(s.tr,{children:[(0,t.jsx)(s.td,{children:(0,t.jsx)(s.code,{children:"setFormValues"})}),(0,t.jsx)(s.td,{children:"Set/Overwrite all values in the form state"}),(0,t.jsx)(s.td,{children:(0,t.jsx)(s.code,{children:"Dispatch<SetStateAction<TFormValues>>"})})]}),(0,t.jsxs)(s.tr,{children:[(0,t.jsx)(s.td,{children:(0,t.jsx)(s.code,{children:"updateFormValues"})}),(0,t.jsx)(s.td,{children:"Updates specific fields within the form state without replacing the entire form data."}),(0,t.jsx)(s.td,{children:(0,t.jsx)(s.code,{children:"(values: Partial<TFormValues>) => void"})})]}),(0,t.jsxs)(s.tr,{children:[(0,t.jsx)(s.td,{children:(0,t.jsx)(s.code,{children:"resetFormValues"})}),(0,t.jsx)(s.td,{children:"Resets the form to its initial default values. Useful for forms that need a clear state on re-entry."}),(0,t.jsx)(s.td,{children:(0,t.jsx)(s.code,{children:"() => void"})})]}),(0,t.jsxs)(s.tr,{children:[(0,t.jsx)(s.td,{children:(0,t.jsx)(s.code,{children:"toStep"})}),(0,t.jsx)(s.td,{children:"Go to a specific step"}),(0,t.jsx)(s.td,{children:(0,t.jsx)(s.code,{children:"(stepName: StepName<TStepNames>, queryParams?: ParsedUrlQuery) => Promise<void | boolean> | void | boolean"})})]}),(0,t.jsxs)(s.tr,{children:[(0,t.jsx)(s.td,{children:(0,t.jsx)(s.code,{children:"toNextStep"})}),(0,t.jsxs)(s.td,{children:["Advance the wizard to the next step using the routing function, and add any query parameters that are passed in. Update the stepper props to reflect the change. Child steps (those which define a ",(0,t.jsx)(s.code,{children:"parentName"}),") are bypassed by ",(0,t.jsx)(s.code,{children:"toNextStep"}),", so this cannot be used to navigate to child steps - you should use ",(0,t.jsx)(s.code,{children:"toStep"})," instead."]}),(0,t.jsx)(s.td,{children:(0,t.jsx)(s.code,{children:"(queryParams?: ParsedUrlQuery) => Promise<void | boolean> | void | boolean"})})]}),(0,t.jsxs)(s.tr,{children:[(0,t.jsx)(s.td,{children:(0,t.jsx)(s.code,{children:"toPreviousStep"})}),(0,t.jsx)(s.td,{children:"Return to the last step that was accessed - this behaves like a browser back button and can be used to navigate back to child steps."}),(0,t.jsx)(s.td,{children:(0,t.jsx)(s.code,{children:"(queryParams?: ParsedUrlQuery) => Promise<void | boolean> | void | boolean"})})]}),(0,t.jsxs)(s.tr,{children:[(0,t.jsx)(s.td,{children:(0,t.jsx)(s.code,{children:"stepperProps"})}),(0,t.jsxs)(s.td,{children:["Props to drive a ",(0,t.jsx)(s.a,{href:"https://storybook-blueprint.vercel.app",children:"Stepper"})," (or your design system's stepper component). See ",(0,t.jsx)(s.a,{href:"#stepperprops",children:"stepperProps"})," for more details"]}
1),(0,t.jsx)(s.td,{children:(0,t.jsx)(s.code,{children:"StepperProps"})})]}),(0,t.jsxs)(s.tr,{children:[(0,t.jsx)(s.td,{children:(0,t.jsx)(s.code,{children:"history"})}),(0,t.jsx)(s.td,{children:"Step History"}),(0,t.jsx)(s.td,{children:(0,t.jsx)(s.code,{children:"Array<StepName<TStepNames>>"})})]}),(0,t.jsxs)(s.tr,{children:[(0,t.jsx)(s.td,{children:(0,t.jsx)(s.code,{children:"setHistory"})}),(0,t.jsx)(s.td,{children:"Manually set the Wizard step history"}),(0,t.jsx)(s.td,{children:(0,t.jsx)(s.code,{children:"Dispatch<SetStateAction<Array<StepName<TStepNames>>>>"})})]}),(0,t.jsxs)(s.tr,{children:[(0,t.jsx)(s.td,{children:(0,t.jsx)(s.code,{children:"resetHistory"})}),(0,t.jsx)(s.td,{children:"Reset the Wizard step history"}),(0,t.jsx)(s.td,{children:(0,t.jsx)(s.code,{children:"() => void"})})]}),(0,t.jsxs)(s.tr,{children:[(0,t.jsx)(s.td,{children:(0,t.jsx)(s.code,{children:"resetWizard"})}),(0,t.jsx)(s.td,{children:"Resets the formValues and step history. Useful for resetting the Wizard after successful onboarding."}),(0,t.jsx)(s.td,{children:(0,t.jsx)(s.code,{children:"() => void"})})]}),(0,t.jsxs)(s.tr,{children:[(0,t.jsx)(s.td,{children:(0,t.jsx)(s.code,{children:"stepNames"})}),(0,t.jsx)(s.td,{children:"Wizard step names."}),(0,t.jsx)(s.td,{children:(0,t.jsx)(s.code,{children:"TStepNames"})})]}),(0,t.jsxs)(s.tr,{children:[(0,t.jsx)(s.td,{children:(0,t.jsx)(s.code,{children:"currentStep"})}),(0,t.jsx)(s.td,{children:"The current step of the wizard"}),(0,t.jsx)(s.td,{children:(0,t.jsx)(s.code,{children:"WizardStep<StepName<TStepNames>> | undefined"})})]})]})]}),"\n",(0,t.jsx)(s.p,{children:"This example shows the functions used for form state management:"}),"\n",(0,t.jsx)(s.pre,{children:(0,t.jsx)(s.code,{className:"language-jsx",children:"const SomeComponent = () => {\n const { formValues, updateFormValues, resetFormValues, clearFormValues } = useWizard();\n\n return (\n <Formik\n initialValues={formValues}\n onSubmit={async (values) => {\n updateFormValues(values);\n }}\n >\n <Form>\n {...Rest of the form}\n <button onClick={resetFormValues}>\n Reset Form\n </button>\n <button onClick={clearFormValues}>\n Clear Form and Storage\n </button>\n </Form>\n </Formik>\n );\n};\n"})}),"\n",(0,t.jsxs)(s.p,{children:["Besides dealing with state and routing logic, the wizard also takes care of providing the standard stepper functionality by exp
1osing the ",(0,t.jsx)(s.code,{children:"stepperProps"})," object which contains:"]}),"\n",(0,t.jsx)(s.h4,{id:"stepperprops",children:(0,t.jsx)(s.code,{children:"stepperProps"})}),"\n",(0,t.jsxs)(s.table,{children:[(0,t.jsx)(s.thead,{children:(0,t.jsxs)(s.tr,{children:[(0,t.jsx)(s.th,{children:"Name"}),(0,t.jsx)(s.th,{children:"Description"}),(0,t.jsx)(s.th,{children:"Type"})]})}),(0,t.jsxs)(s.tbody,{children:[(0,t.jsxs)(s.tr,{children:[(0,t.jsx)(s.td,{children:(0,t.jsx)(s.code,{children:"activeStep"})}),(0,t.jsx)(s.td,{children:"current step in wizard journey."}),(0,t.jsxs)(s.td,{children:[(0,t.jsx)(s.code,{children:"number"})," | ",(0,t.jsx)(s.code,{children:"undefined"})]})]}),(0,t.jsxs)(s.tr,{children:[(0,t.jsx)(s.td,{children:(0,t.jsx)(s.code,{children:"totalSteps"})}),(0,t.jsx)(s.td,{children:"number of main steps in wizard config."}),(0,t.jsx)(s.td,{children:(0,t.jsx)(s.code,{children:"number"})})]}),(0,t.jsxs)(s.tr,{children:[(0,t.jsx)(s.td,{children:(0,t.jsx)(s.code,{children:"onStepClicked"})}),(0,t.jsx)(s.td,{children:"will navigate you to the respective step."}),(0,t.jsx)(s.td,{children:(0,t.jsx)(s.code,{children:"Function"})})]}),(0,t.jsxs)(s.tr,{children:[(0,t.jsx)(s.td,{children:(0,t.jsx)(s.code,{children:"stepConfigs"})}),(0,t.jsx)(s.td,{children:"Generated config for each step in the stepper, parsed from the steps passed to the WizardProvider. The disabled state of the stepConfigs are injected with the route guarding functionality, which means that unreachable steps are disabled."}),(0,t.jsx)(s.td,{children:(0,t.jsx)(s.code,{children:"(StepConfig | undefined)[] | undefined"})})]})]})]}),"\n",(0,t.jsx)(s.p,{children:"Therefore, setting up a stepper becomes very easy as shown in the example below."}),"\n",(0,t.jsx)(s.pre,{children:(0,t.jsx)(s.code,{className:"language-tsx",children:"export const WizardStepper: React.FC = () => {\n const { stepperProps } = useWizard();\n return <Stepper {...stepperProps} />;\n};\n"})}),"\n",(0,t.jsx)(s.h2,{id:"wizardprovider",children:(0,t.jsx)(s.em,{children:(0,t.jsx)(s.code,{children:"WizardProvider"})})}),"\n",(0,t.jsxs)(s.p,{children:["The wizard context provider holds the state for the form wizard. This is both the form values and the step values, which could be generated at least partially by the values the user enters into the form fields.\nBesides the ",(0,t.jsx)(s.code,{children:"steps"}),", two routing related props, ",(0,t.jsx)(s.code,{children:"pathname"})," and ",(0,t.jsx)(s.code,{children:"routingFn"})," must be defined, since the wizard is designed to be framework agnostic."]}),"\n",(0,t.jsx)(s.h3,{id:"props",children:"props"}),"\n",(0,t.jsxs)(s.table,{children:[(0,t.jsx)(s.thead,{children:(0,t.jsxs)(s.tr,{children:[(0,t.jsx)(s.th,{children:"Name"}),(0,t.jsx)(s.th,{children:"Description"}),(0,t.jsx)(s.th,{children:"Type"})]})}),(0,t.jsxs)(s.tbody,{children:[(0,t.jsxs)(s.tr,{children:[(0,t.jsx)(s.td,{children:(0,t.jsx)(s.code,{children:"pathname"})}),(0,t.jsx)(s.td,{children:"the current pathname."}),(0,t.jsx)(s.td,{children:(0,t.jsx)(s.code,{children:"string"})})]}),(0,t.jsxs)(s.tr,{children:[(0,t.jsx)(s.td,{children:(0,t.jsx)(s.code,{children:"routingFn"})}),(0,t.jsx)(s.td,{children:"the routing function to go to a specific route."}),(0,t.jsx)(s.td,{children:(0,t.jsx)(s.code,{children:"(url: UrlObject) => Promise<void | boolean> | void | boolean"})})]}),(0,t.jsxs)(s.tr,{children:[(0,t.jsx)(s.td,{children:(0,t.jsx)(s.code,{children:"steps"})}),(0,t.jsx)(s.td,{children:"Array of steps in the wizard, or function to generate array of steps in the wizard."}),(0,t.jsx)(s.td,{children:(0,t.jsx)(s.code,{children:"WizardStepsConfig<StepName<TStepNames>, TFormValues>"})})]})]})]}),"\n",(0,t.jsx)(s.p,{children:"For NextJS applications, this may look like this."}),"\n",(0,t.jsx)(s.pre,{children:(0,t.jsx)(s.code,{className:"language-tsx",children:"export const Provider: FC<PropsWithChildren> = ({ children }) => {\n const router = useRouter();\n return (\n <WizardProvider\n pathname={router.pathname}\n routingFn={router.push}\n steps={steps}\n >\n {children}\n </WizardProvider>\n );\n};\n"})}),"\n",(0,t.jsx)(s.p,{children:"We'll now discuss steps in more depth, and look at some different options for generating wizards thst have hierarchies of steps and conditional routing though the wizard."}),"\n",(0,t.jsx)(s.h3,{id:"steps",children:"steps"}),"\n",(0,t.jsxs)(s.p,{children:["The steps config defines the step ordering and hierarchy, as well as some optional meta information for a ",(0,t.jsx)(s.a,{href:"https://storybook-blueprint.vercel.app",children:"Stepper"})," (or your design system's stepper component). For each step you must define a ",(0,t.jsx)(s.code,{children:"name"})," as an identifier and a ",(0,t.jsx)(s.code,{children:"pathname"}
1)," for navigation. For steps that you wish to show in a stepper, and which form the primary steps for your navigation, you can additionally provide a ",(0,t.jsx)(s.code,{children:"stepConfig"})," which gets injected into the ",(0,t.jsx)(s.code,{children:"stepConfigs"})," attribute for each step in the ",(0,t.jsx)(s.a,{href:"#stepperprops",children:"stepperProps"})," returned from ",(0,t.jsx)(s.code,{children:"useWizard"}),"."]}),"\n",(0,t.jsx)(s.h4,{id:"step-hierarchy",children:"Step hierarchy"}),"\n",(0,t.jsxs)(s.p,{children:["If you want to render the same step selected in the ",(0,t.jsx)(s.a,{href:"https://storybook-blueprint.vercel.app",children:"Stepper"})," (or your design system's stepper component) for multiple steps, you can link child steps to a 'parent' using the ",(0,t.jsx)(s.code,{children:"parentName"})," attribute. Each child step must have a parent step as an entry point if you want to be able to render the ",(0,t.jsx)(s.a,{href:"https://storybook-blueprint.vercel.app",children:"Stepper"})," (or your design system's stepper component) for those steps."]}),"\n",(0,t.jsxs)(s.p,{children:["Here is an example of what a simple set of steps could look like where we only need to show the ",(0,t.jsx)(s.code,{children:"StepName.SupplyChildStep"})," if we get a particular response from the user/api in ",(0,t.jsx)(s.code,{children:"StepName.Supply"}),"."]}),"\n",(0,t.jsx)(s.pre,{children:(0,t.jsx)(s.code,{className:"language-tsx",children:"const stepsConfig = [\n {\n name: StepName.Supply,\n pathname: '/onboarding/1-supply',\n stepConfig: {\n title: 'Supply',\n },\n },\n // Here an example of how a child step could be setup\n {\n name: StepName.SupplyChildStep,\n parentName: StepName.Supply,\n pathname: '/onboarding/1-supply/child-step',\n },\n {\n name: StepName.TariffSelection,\n pathname: '/onboarding/2-tariff-selection',\n stepConfig: {\n title: 'Tariff Selection',\n },\n },\n {\n name: StepName.DetailsPersonal,\n pathname: '/onboarding/3-personal-details',\n stepConfig: {\n title: 'Details',\n },\n },\n] as const satisfies WizardSteps<StepName>;\n"})}),"\n",(0,t.jsxs)(s.p,{children:["For more advanced use cases, you can also provide a function, which gets the form values as an input and returns a set of steps based on those values.\nFor example, the steps could be based on a tariff choice. You'd use this option (rather just using parent/child steps) for proving a really different set of steps between those two options,\nwhere you'd want the ",(0,t.jsx)(s.a,{href:"https://storybook-blueprint.vercel.app",children:"Stepper"})," (or your design system's stepper component) to update to show different steps based on the choice."]}),"\n",(0,t.jsx)(s.pre,{children:(0,t.jsx)(s.code,{className:"language-tsx",children:"const getStepsConfig = (formValues: FormValues) => {\n if (formValues.tariffName === 'octo_basic') {\n return stepsConfigA;\n }\n return stepsConfigB;\n};\n"})}),"\n",(0,t.jsx)(s.h4,{id:"wizardsteptstepname",children:(0,t.jsx)(s.code,{children:"WizardStep<TStepName>"})}),"\n",(0,t.jsxs)(s.table,{children:[(0,t.jsx)(s.thead,{children:(0,t.jsxs)(s.tr,{children:[(0,t.jsx)(s.th,{children:"Name"}),(0,t.jsx)(s.th,{children:"Description"}),(0,t.jsx)(s.th,{children:"Type"})]})}),(0,t.jsxs)(s.tbody,{children:[(0,t.jsxs)(s.tr,{children:[(0,t.jsx)(s.td,{children:(0,t.jsx)(s.code,{children:"name"})}),(0,t.jsx)(s.td,{children:"An internal name for the step."}),(0,t.jsx)(s.td,{children:(0,t.jsx)(s.code,{children:"TStepName"})})]}),(0,t.jsxs)(s.tr,{children:[(0,t.jsx)(s.td,{children:(0,t.jsx)(s.code,{children:"parentName?"})}),(0,t.jsx)(s.td,{children:"An optional name for a parent step. If provided, we can think of the step as being a child of the parent step."}),(0,t.jsx)(s.td,{children:(0,t.jsx)(s.code,{children:"TStepName"})})]}),(0,t.jsxs)(s.tr,{children:[(0,t.jsx)(s.td,{children:(0,t.jsx)(s.code,{children:"pathname"})}),(0,t.jsx)(s.td,{children:"Route to the step as defined in the application."}),(0,t.jsx)(s.td,{children:(0,t.jsx)(s.code,{children:"string"})})]}),(0,t.jsxs)(s.tr,{children:[(0,t.jsx)(s.td,{children:(0,t.jsx)(s.code,{children:"queryParams"})}),(0,t.jsxs)(s.td,{children:["query parameters to add to the ",(0,t.jsx)(s.code,{children:"pathname"}),". Can be static or dynamic - see ",(0,t.jsx)(s.a,{href:"#queryparams",children:"queryParams"})]}),(0,t.jsx)(s.td,{children:(0,t.jsx)(s.code,{children:"ParsedUrlQuery"})})]}),(0,t.jsxs)(s.tr,{children:[(0,t.jsx)(s.td,{children:(0,t.jsx)(s.code,{children:"stepConfig?"})}),(0,t.jsxs)(s.td,{children:["optional values to pass to the ",(0,t.jsx)(s.a,{href:"https://storybook-blueprint.vercel.app",children:"Stepper"})," (or your design system's stepper component) component for this step. Adding this object is what causes steps to appear in the ",(0,t.jsx)(s.code,{children:"stepperProps"}),", which should then be passed to the ",(0,t.jsx)(s.a,{href:"https://storybook-blueprint.vercel.app",children:"Stepper"})," (or your design system's stepper component) component - see ",(0,t.jsx)(s.a,{href:"#stepperprops",children:"stepperProps"})," for an example of this."]}),(0,t.jsx)(s.td,{children:(0,t.jsx)(s.code,{children:"StepConfig"})})]})]})]}),"\n",(0,t.jsxs)(s.p,{children:["The ",(0,t.jsx)(s.code,{children:"StepConfig"})," type comes from ",(0,t.jsx)(s.a,{href:"https://storybook-blueprint.vercel.app#full-api",children:"StepConfig"}),", but these are the main props:"]}),"\n",(0,t.jsxs)(s.table,{children:[(0,t.jsx)(s.thead,{children:(0,t.jsxs)(s.tr,{children:[(0,t.jsx)(s.th,{children:"Name"}),(0,t.jsx)(s.th,{children:"Description"}),(0,t.jsx)(s.th,{children:"Type"})]})}),(0,t.jsxs)(s.tbody,{children:[(0,t.jsxs)(s.tr,{children:[(0,t.jsx)(s.td,{children:(0,t.jsx)(s.code,{children:"title?"})}),(0,t.jsx)(s.td,{children:"The title for the step."}),(0,t.jsx)(s.td,{children:(0,t.jsx)(s.code,{children:"string"})})]}),(0,t.jsxs)(s.tr,{children:[(0,t.jsx)(s.td,{children:(0,t.jsx)(s.code,{children:"description?"})}),(0,t.jsx)(s.td,{children:"The description for the step."}),(0,t.jsx)(s.td,{children:(0,t.jsx)(s.code,{children:"((isCurrentStep: boolean) => React.ReactNode) | string"})})]}),(0,t.jsxs)(s.tr,{children:[(0,t.jsx)(s.td,{children:(0,t.jsx)(s.code,{children:"icon"})}),(0,t.jsx)(s.td,{children:"A custom icon instead of the numbered step."}),(0,t.jsx)(s.td,{children:(0,t.jsx)(s.code,{children:"IconName"})})]})]})]}),"\n",(0,t.jsx)(s.h4,{id:"queryparams",children:"queryParams"}),"\n",(0,t.jsxs)(s.p,{children:["Steps can optionally include a ",(0,t.jsx)(s.code,{children:"queryParams"})," field that can either be static using a constant definition, or dynamic using a function definition."]}),"\n",(0,t.jsx)(s.pre,{children:(0,t.jsx)(s.code,{className:"language-tsx",children:"// static query params\nconst stepsConfig = [\n {\n name: StepName.EditPersonalDetails,\
1n pathname: '/onboarding/3-personal-details',\n queryParams: {\n edit: 'true',\n },\n ...\n] as const satisfies WizardSteps<StepName>;\n\n// dynamic query params\nconst getStepsConfig = (formValues: FormValues) => [\n {\n name: StepName.Supply,\n pathname: '/onboarding/1-supply',\n queryParams: {\n affiliate: formValues.affiliateCode,\n },\n ...\n] as const satisfies WizardSteps<StepName>;\n"})}),"\n",(0,t.jsx)(s.h2,{id:"wizardformhydrator",children:(0,t.jsx)(s.em,{children:(0,t.jsx)(s.code,{children:"WizardFormHydrator"})})}),"\n",(0,t.jsx)(s.p,{children:"This component is placed anywhere in the formik context, to ensure that formik is hydrated properly, after the client mounts."}),"\n",(0,t.jsx)(s.pre,{children:(0,t.jsx)(s.code,{className:"language-tsx",children:"const WizardStep = () => {\n const { formValues, setFormValues, toNextStep } = useWizard();\n\n return (\n <Formik {...}>\n <Form>\n <WizardFormHydrator />\n ...\n </Form>\n </Formik>\n );\n};\n"})}),"\n",(0,t.jsxs)(s.p,{children:["With the ",(0,t.jsx)(s.code,{children:"transformBeforeHydration"})," prop you can provide a function, which gives you control over the ",(0,t.jsx)(s.code,{children:"formValues"})," before they are hydrated in to the form. One example where this can be useful, is when you want to inject query params, as shown in the example below."]}),"\n",(0,t.jsx)(s.pre,{children:(0,t.jsx)(s.code,{className:"language-tsx",children:"type TransformFn =\n WizardFormHydratorProps<FormValues>['transformBeforeHydration'];\n\nconst SomeOnboardingPage = () => {\n const router = useRouter();\n\n const transformFn: TransformFn = (formValues) => {\n return {\n ...formValues,\n xyz:\n typeof router.query.xyz === 'string'\n ? router.query.xyz\n : formValues.xyz,\n };\n };\n\n return (\n <Formik>\n <Form>\n <WizardFormHydrator transformBeforeHydration={transformFn} />\n ...\n </Form>\n </Formik>\n );\n};\n"})}),"\n",(0,t.jsx)(s.h2,{id:"wizardrouteguard",children:(0,t.jsx)(s.em,{children:(0,t.jsx)(s.code,{children:"WizardRouteGuard"})})}),"\n",(0,t.jsxs)(s.p,{children:["The ",(0,t.jsx)(s.code,{children:"WizardRouteGuard"})," is an optional piece of functionality, which mainly is used to prevent users from manually skipping steps. It can also be used as a skew protection, if a new version of the journey is deployed and stored data becomes invalid. The ",(0,t.jsx)(s.code,{children:"WizardRouteGuard"})," can be placed anywhere as long as it's within the ",(0,t.jsx)(s.code,{children:"WizardProvider"}),". It supports two props ",(0,t.jsx)(s.code,{children:"validateStep"})," and ",(0,t.jsx)(s.code,{children:"onInvalidStep"}),"."]}),"\n",(0,t.jsx)(s.pre,{children:(0,t.jsx)(s.code,{className:"language-tsx",children:"<OnboardingWizardRouteGuard\n validateStep={...}\n onInvalidStep={...}\n/>\n"})}),"\n",(0,t.jsxs)(s.p,{children:["With help of the ",(0,t.jsx)(s.code,{children:"wizardRouteGuardHelpers"})," these can be configured very flexible way as will be shown in the subsequent subsections."]}),"\n",(0,t.jsx)(s.pre,{children:(0,t.jsx)(s.code,{className:"language-tsx",children:"const {\n composeValidators,\n validateFormValues,\n redirectToLastValidStepInHistory,\n validateLinearHistory,\n} = wizardRouteGuardHelpers;\n"})}),"\n",(0,t.jsx)(s.h3,{id:"validatestep",children:(0,t.jsx)(s.em,{children:(0,t.jsx)(s.code,{children:"validateStep"})})}),"\n",(0,t.jsxs)(s.p,{children:["The function passed to the ",(0,t.jsx)(s.code,{children:"validateStep"})," prop determines the validation logic. For some projects using only the ",(0,t.jsx)(s.code,{children:"validateLinearHistory"})," helper may suffice, as it guarantees that the steps are traversed in the correct order and without skipping steps. However, since it doesn't validate submitted values, it allows manually navigating to the next step even without valid submission which for some usages may be insufficient. To mitigate this, we can combine multiple validators with the ",(0,t.jsx)(s.code,{children:"composeValidators"})," helper function and add in additional validation logic for the ",(0,t.jsx)(s.code,{children:"formValues"}),". This can be done in multiple ways, easiest of which is with the ",(0,t.jsx)(s.code,{children:"val
1idateFormValues.fromSteps(steps)"})," helper. For this to work you will have to modify the step config to contain a ",(0,t.jsx)(s.code,{children:"validate"})," function in each step. For accurate typing the types ",(0,t.jsx)(s.code,{children:"ValidatedWizardSteps"})," and ",(0,t.jsx)(s.code,{children:"ValidatedWizardStepsConfig"})," are exposed."]}),"\n",(0,t.jsx)(s.pre,{children:(0,t.jsx)(s.code,{className:"language-tsx",children:"const steps = [\n {\n name: ...,\n pathname: ...,\n stepConfig: ...,\n validate: ({ formValues }) => { ... }, // This fn must return a boolean\n },\n ...\n] satisfies ValidatedWizardStepsConfig<StepName, OnboardingValues>;\n"})}),"\n",(0,t.jsxs)(s.p,{children:["The ",(0,t.jsx)(s.code,{children:"validateFormValues.fromSteps(steps)"})," helper then extracts the validators from the steps config. The result logic may look like this:"]}),"\n",(0,t.jsx)(s.pre,{children:(0,t.jsx)(s.code,{className:"language-tsx",children:"export const RouteGuardExample = () => {\n const validateStep = composeValidators(\n // Validate that steps are completed in the correct order.\n validateLinearHistory,\n // Validate form values. The validation functions are defined in the steps.\n validateFormValues.fromSteps(steps)\n );\n\n return (\n <WizardRouteGuard\n validateStep={validateStep}\n onInvalidStep={redirectToLastValidStepInHistory}\n />\n );\n};\n"})}),"\n",(0,t.jsxs)(s.p,{children:["Alternatively, a map of validation functions can be defined instead of embedding validation in the steps config, and then used with the ",(0,t.jsx)(s.code,{children:"validateFormValues.fromMap(validatorMap)"})," helper. This approach can be useful to reduce redundancy of validation function for dynamic steps."]}),"\n",(0,t.jsx)(s.h3,{id:"oninvalidstep",children:(0,t.jsx)(s.em,{children:(0,t.jsx)(s.code,{children:"onInvalidStep"})})}),"\n",(0,t.jsxs)(s.p,{children:["When the validator returns false, then the ",(0,t.jsx)(s.code,{children:"onInvalidStep"})," function is called. For most use cases the desired action is to navigate to the last valid step, which can be done with the ",(0,t.jsx)(s.code,{children:"redirectToLastValidStepInHistory"})," helper function. If additional custom logic is required, any custom function can be passed which provides full flexibility."]})]})}function h(e={}){const{wrapper:s}={...(0,i.R)(),...e.components};return s?(0,t.jsx)(s,{...e,children:(0,t.jsx)(c,{...e})}):c(e)}},5907:(e,s,n)=>{n.d(s,{R:()=>a,x:()=>d});var r=n(9430);const t={},i=r.createContext(t);function a(e){const s=r.useContext(i);return r.useMemo(function(){return"function"==typeof e?e(s):{...s,...e}},[s,e])}function d(e){let s;return s=e.disableParentContext?"function"==typeof e.components?e.components(t):e.components||t:a(e.components),r.createElement(i.Provider,{value:s},e.children)}}}]);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.