View Docs
Step-by-Step Definition of context

Step-by-Step Definition of context

In @use-funnel, you need to define the type of context required at each step (step). Let's look at different ways to define context.

Defining with generics

Define the type of context required for each step as a generic, and pass the object type with step as the key and the type of context as the value to the generics of useFunnel().

import { useFunnel } from "@use-funnel/next";
 
// 1. Nothing entered
type EmailInput = { email?: string; password?: string; other?: unknown }
// 2. Email entered
type PasswordInput = { email: string; password?: string; other?: unknown }
// 3. Email and password entered
type OtherInfoInput = { email: string; password: string; other?: unknown }
 
function MyFunnelApp() {
  const funnel = useFunnel<{
    EmailInput: EmailInput;
    PasswordInput: PasswordInput;
    OtherInfoInput: OtherInfoInput;
  }>({
    id: "how-to-define-step-contexts",
    initial: {
      step: "EmailInput",
      context: {}
    }
  });
 
  funnel.step === 'EmailInput' && typeof funnel.context.email // "string" | "undefined"
  funnel.step === 'PasswordInput' && typeof funnel.context.email // "string"
 
  funnel.step === 'PasswordInput' && typeof funnel.context.password // "string" | "undefined"
  funnel.step === 'OtherInfoInput' && typeof funnel.context.password // "string"
  // ...
}

Defining with steps object

You can define step through FunnelStepOption of useFunnel(). Define context through guard() or parse().

import { useFunnel } from "@use-funnel/next";
 
type FormState = {
  email?: string;
  password?: string;
  other?: unknown;
}
 
function EmailInput_guard (data: unknown): data is FormState {
  return typeof data === 'object' && typeof data.email === 'string';
}
 
function PasswordInput_parse (data: unknown): FormState & { password: string } {
  if (!(data != null && typeof data === 'object' && 'password' in data)) {
    throw new Error('Invalid passwordInput data');
  }
  return data;
}
 
function OtherInfoInput_parse (data: unknown): FormState & { password: string; other: unknown } {
  const parseData = PasswordInput_parse(data);
  if (!('other' in data)) {
    throw new Error('Invalid otherInfoInput data');
  }
  return parseData;
}
 
function MyFunnelApp() {
  const funnel = useFunnel<FormState>({
    id: "how-to-define-step-contexts",
    initial: {
      step: "EmailInput",
      context: {}
    },
    steps: {
      EmailInput: { guard: EmailInput_guard },
      PasswordInput: { parse: PasswordInput_parse },
      OtherInfoInput: { parse: OtherInfoInput_parse },
    }
  });
 
  funnel.step === 'EmailInput' && typeof funnel.context.email // "string" | "undefined"
  funnel.step === 'PasswordInput' && typeof funnel.context.email // "string"
 
  funnel.step === 'PasswordInput' && typeof funnel.context.password // "string" | "undefined"
  funnel.step === 'OtherInfoInput' && typeof funnel.context.password // "string"
  // ...
}
  • guard (function, optional): A function that checks if the data is of the context type of that step.
  • parse (function, optional): A function that converts data to the context type of that step.
💡

If both are defined, guard() is executed first.

createFunnelSteps() to define

You can create a step that changes a specific key of the initial context from optional to required when transitioning to the next step simply.

declare function createFunnelSteps<FormState>(): {
  extends: (name: string | string[], options?: { requiredKeys: string | string[] }) => this;
  build: () => Record<string, FunnelStepOption>;
}
  • extends (function): Extends a new funnel step.

    • name (string): The name of the new funnel step.
    • options (object, optional): Options for the new funnel step.
      • requiredKeys (string): The key to be required in the initial context.
  • build (function): Builds the funnel steps.

    • Returns (Record<string, FunnelStepOption>): An object with the step name as the key and the options of each step as the value.
import { createFunnelSteps, useFunnel } from "@use-funnel/next";
 
type FormState = {
  email?: string;
  password?: string;
  other?: unknown;
}
 
const steps = createFunnelSteps<FormState>()
  .extends('EmailInput')
  .extends('PasswordInput', { requiredKeys: 'email' })
  .extends('OtherInfoInput', { requiredKeys: 'password' })
  .build();
 
function MyFunnelApp() {
  const funnel = useFunnel({
    id: "create-funnel-steps",
    initial: {
      step: "EmailInput",
      context: {}
    },
    steps
  });
 
  funnel.step === 'EmailInput' && typeof funnel.context.email // "string" | "undefined"
  funnel.step === 'PasswordInput' && typeof funnel.context.email // "string"
 
  funnel.step === 'PasswordInput' && typeof funnel.context.password // "string" | "undefined"
  funnel.step === 'OtherInfoInput' && typeof funnel.context.password // "string"
  // ...
}

Using Runtime Validator Packages

Use runtime validator packages to validate data and ensure type safety. Here are examples using zod and superstruct.

import { z } from 'zod';
 
const emailInputSchema = z.object({
  email: z.string(),
  password: z.string(),
  other: z.unknown()
}).partial();
 
const passwordInputSchema = emailInputSchema.required({ email: true });
const otherInfoInputSchema = passwordInputSchema.required({ password: true });
 
function MyFunnelApp() {
  const funnel = useFunnel({
    id: "zod-example",
    steps: {
      EmailInput: { parse: emailInputSchema.parse },
      PasswordInput: { parse: passwordInputSchema.parse },
      OtherInfoInput: { parse: otherInfoInputSchema.parse },
    },
    initial: {
      step: "EmailInput",
      context: {}
    }
  });
 
  funnel.step === 'EmailInput' && typeof funnel.context.email // "string" | "undefined"
  funnel.step === 'PasswordInput' && typeof funnel.context.email // "string"
 
  funnel.step === 'PasswordInput' && typeof funnel.context.password // "string" | "undefined"
  funnel.step === 'OtherInfoInput' && typeof funnel.context.password // "string"
  // ...
}

Runtime Validator Packages? These packages validate data and ensure type safety at runtime. They are used for form input validation, API response validation, etc.