Getting Started
Let's implement a simple sign-up flow using @use-funnel
. You'll learn how to define and manage the state of each step safely and step by step.
Defining context step by step
The sign-up process can be divided into several steps. Here, we'll divide it into three steps: email input, password input, and other information input. And we'll define the state required for each step as follows.
// 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 }
-
EmailInput
: The first step of sign-up. Although there are email and password input fields, nothing has been entered yet. Bothemail
andpassword
are defined as optional fields. -
PasswordInput
: The second step of sign-up. After the user enters the email, they enter the password. In this step, theemail
field must be entered, and thepassword
is an optional field. -
OtherInfoInput
: The third step of sign-up. After the user enters both the email and password, they enter additional information. In this step, bothemail
andpassword
must be entered.
By defining the state of each step as a type, you can maintain type safety in your code and easily track the information required for each step.
Configuring the initial step
Now let's set the initial step using useFunnel()
.
First, specify the object type with the key as the step and the context object as the value in the generics of useFunnel()
. Pass the type you defined for each step in the previous step to useFunnel()
. Set the inital step and context object to be used when entering the component in the initial
property.
Here, we set the initial step to "EmailInput" and an empty context
object to be used in that step. id
is a unique identifier to distinguish when there are multiple funnels in one component.
import { useFunnel } from "@use-funnel/next";
import type { EmailInput, PasswordInput, OtherInfoInput } "./context";
function MyFunnelApp() {
const funnel = useFunnel<{
EmailInput: EmailInput;
PasswordInput: PasswordInput;
OtherInfoInput: OtherInfoInput;
}>({
id: "my-funnel-app",
initial: {
step: "EmailInput",
context: {}
}
});
// ...
}
For other ways to define the state of each step, see the state definition guide.
Using context and history step by step
useFunnel()
returns context
and history
based on the step
. You can configure the UI for each step and manage the required state and events.
declare function EmailInput(props: { onNext: (email: string) => void }): JSX.Element;
declare function PasswordInput(props: { onNext: (password: string) => void }): JSX.Element;
declare function OtherInfoInput(props: { onNext: (other: unknown) => void }): JSX.Element;
switch (funnel.step) {
case "EmailInput":
return <EmailInput onNext={(email) => funnel.history.push("PasswordInput", { email })} />;
case "PasswordInput":
return <PasswordInput email={funnel.context.email} onNext={(password) => funnel.history.push("OtherInfoInput", { ...funnel.context, password })} />;
case "OtherInfoInput":
return <OtherInfoInput onNext={(other) => funnel.history.push("Finish", { ...funnel.context, other })} />;
}
funnel.context
: You can get thecontext
of the currentstep
. For example, in the "EmailInput" step,funnel.context.email
is of typestring | undefined
, but in the "PasswordInput" step, it can be inferred as typestring
.funnel.history.push()
: You can move to the next step. Pass the desiredstep
as the first argument topush()
and the necessarycontext
for thatstep
as the second argument.funnel.history.replace()
: It's basic behavior is similar tofunnel.history.push()
, but it replaces the currentstep
without adding to the history stack.
Note: Implementing easily using <funnel.Render />
To centralize the rendering logic for each step, you can use <funnel.Render />
. This component allows you to define the rendering logic for each step and pass the necessary data for each step.
return (
<funnel.Render
EmailInput={({ history }) => (
<EmailInput onNext={(email) => history.push("PasswordInput", { email })} />
)}
PasswordInput={({ context, history }) => (
<PasswordInput email={context.email} onNext={(password) => history.push("OtherInfoInput", { ...context, password })} />
)}
OtherInfoInput={({ context, history }) => (
<OtherInfoInput onNext={(other) => history.push("Finish", { ...context, other })} />
)}
/>
);
The detailed usage of the <funnel.Render />
component is available in the reference.