Research grant application
The form changes shape for individual and collective applicants. It also changes the settlement and reporting fields for the selected routes.
Try the complete flow
Choose a collective and the registered representation path. Search the fake
registry and apply a result with typed form.api updates. Change the payout
route to resolve another conditional branch.
Branching application
Applicant identity, registry lookup, settlement route, reporting rules, and a two-request submission all share one typed form state.
What this form demonstrates
- Conditional fields across several nested objects.
- An application-owned registry search inside the form.
- Read-only values populated through
form.api.setValue. useWatchfor conditional application UI and a live preview.- Two sequential mutations from transformed schema output.
- Preserved values when the selected branch changes.
Copy the source
"use client"
import {
,
,
,
,
} from "@tanstack/react-query"
import {
,
type ,
type ,
type ,
} from "form-please"
import { } from "form-please/default-slots"
import { } from "form-please/native-controls"
import { } from "react"
import { } from "zod"
const =
.({
: .(["person", "collective"]),
: .({
: .().(2, "Enter the lead applicant's name"),
: .().("Enter a valid email"),
}),
: .(["local", "international"]),
: .({
: .(["registered", "forming"]).(),
: .().(),
: .().(),
: .().(),
}),
: .({
: .(["research", "public-program", "education"]),
: .().(5, "Use a descriptive project title"),
: .().(80, "Write at least 80 characters"),
: .().(1_000).(250_000),
: .().().(1).(36),
}),
: .({
: .(["bank", "digital-wallet"]),
: .().(),
: .().(),
}),
: .({
: .(["registered", "exempt", "pending"]),
: .().(),
}),
: .(),
})
.((, ) => {
if (. === "collective") {
if (.. === ) {
.({
: "custom",
: ["organization", "path"],
: "Choose how the collective is represented",
})
}
if (
.. === "registered" &&
.. ===
) {
.({
: "custom",
: ["organization", "registryId"],
: "Select a registry record",
})
}
if (
.. === "forming" &&
(.. ?? "").(). < 2
) {
.({
: "custom",
: ["organization", "name"],
: "Enter the collective's working name",
})
}
}
if (
.. === "bank" &&
(.. ?? "").(). < 8
) {
.({
: "custom",
: ["payout", "bankAccount"],
: "Enter a valid settlement account",
})
}
if (
.. === "digital-wallet" &&
(.. ?? "").(). < 3
) {
.({
: "custom",
: ["payout", "walletHandle"],
: "Enter a wallet handle",
})
}
if (
.. === "registered" &&
(.. ?? "").(). < 4
) {
.({
: "custom",
: ["reporting", "reference"],
: "Enter the reporting reference",
})
}
if (!.) {
.({
: "custom",
: ["confirmAccuracy"],
: "Confirm the application before sending it",
})
}
})
.(() => ({
...,
: {
....,
: ...(),
},
: `${..}:${..}`,
}))
type = <typeof >
type = <typeof >
type = {
readonly : string
readonly : string
readonly : string
}
const : readonly [] = [
{ : "arc-104", : "Open Field Assembly", : "CA" },
{ : "arc-208", : "Night School Cooperative", : "DE" },
{ : "arc-319", : "Public Signal Workshop", : "NZ" },
]
const = {
: "person",
: { : "Mina Park", : "mina@example.test" },
: "local",
: {
: ,
: ,
: ,
: ,
},
: {
: "research",
: "A public atlas of overlooked urban sounds",
:
"We will record, annotate, and publish an accessible field archive with neighborhood listening sessions and an open teaching kit.",
: 42_000,
: 9,
},
: {
: "bank",
: "SETTLE-482910",
: ,
},
: { : "pending", : },
: false,
} satisfies
const = ({
: (),
: (),
})
type = <typeof >
function ({
,
,
}: {
readonly :
readonly : string | undefined
}) {
const [, ] = ("")
const = ({
: ["grant-registry", ],
: () =>
(
.(() =>
..().(.().()),
),
),
: 30_000,
})
return (
<
="form-please-complex__embedded"
="Registry search"
>
<>
Search the independent registry
<
={() => (..)}
="Try Open or School"
="search"
={}
/>
</>
< ="form-please-complex__choice-list">
{. && <>Checking records…</>}
{.?.(() => (
<
={ === .}
={.}
={() => {
..("organization.registryId", .)
..("organization.name", .)
..(
"organization.registrationCountry",
.,
)
}}
="button"
>
{.} · {.}
</>
))}
</>
</>
)
}
const = .(, {
: [
{
: "section",
: "applicant",
: "Applicant",
: "Choose the legal path before entering dependent details.",
: 2,
: [
{
: "field",
: "applicantKind",
: "select",
: "Applying as",
: [
{ : "person", : "An individual" },
{ : "collective", : "A collective" },
],
},
{
: "field",
: "jurisdiction",
: "select",
: "Administrative scope",
: [
{ : "local", : "Domestic" },
{ : "international", : "Cross-border" },
],
},
{
: "field",
: "contact.name",
: "text",
: "Lead applicant",
: true,
},
{
: "field",
: "contact.email",
: "text",
: "Contact email",
: { : "email" },
: true,
},
],
},
{
: "section",
: "collective",
: "Collective identity",
: ({ }) => === "collective",
: 2,
: [
{
: "field",
: "organization.path",
: "select",
: "Representation",
: [
{ : "registered", : "Registered collective" },
{ : "forming", : "Collective in formation" },
],
: {
: { : "Choose a path", : true },
},
},
{
: "field",
: "organization.registryId",
: "text",
: "Registry record ID",
: () => .. === "registered",
: true,
},
{
: "field",
: "organization.name",
: "text",
: () => {
if (.. === "registered")
return "Registered name"
return "Working name"
},
: () => .. !== ,
: () => .. === "registered",
},
{
: "field",
: "organization.registrationCountry",
: "text",
: "Registration country",
: () => .. !== ,
: () => .. === "registered",
},
],
},
{
: "section",
: "project",
: "Proposed work",
: 2,
: [
{
: "field",
: "project.stream",
: "select",
: "Funding stream",
: [
{ : "research", : "Independent research" },
{ : "public-program", : "Public program" },
{ : "education", : "Open education" },
],
},
{
: "field",
: "project.title",
: "text",
: "Project title",
: true,
},
{
: "field",
: "project.abstract",
: "textarea",
: "Abstract",
:
"At least 80 characters; this becomes the public summary.",
: "full",
: true,
: { : 5 },
},
{
: "field",
: "project.requestedFunds",
: "number",
: "Requested funds",
: { : 1_000, : 250_000, : 500 },
},
{
: "field",
: "project.durationMonths",
: "number",
: "Duration in months",
: { : 1, : 36, : 1 },
},
],
},
{
: "section",
: "settlement",
: "Settlement and reporting",
: 2,
: [
{
: "field",
: "payout.method",
: "select",
: "Disbursement route",
: [
{ : "bank", : "Settlement account" },
{ : "digital-wallet", : "Digital wallet" },
],
},
{
: "field",
: "payout.bankAccount",
: "text",
: "Settlement account",
: () => .. === "bank",
},
{
: "field",
: "payout.walletHandle",
: "text",
: "Wallet handle",
: () => .. === "digital-wallet",
},
{
: "field",
: "reporting.status",
: "select",
: "Reporting status",
: [
{ : "registered", : "Registered" },
{ : "exempt", : "Exempt" },
{ : "pending", : "Pending" },
],
},
{
: "field",
: "reporting.reference",
: "text",
: "Reporting reference",
: () => .. === "registered",
},
{
: "field",
: "confirmAccuracy",
: "checkbox",
: "I confirm that the application is accurate",
: "full",
},
],
},
],
})
export function () {
const [] = (
() =>
new ({
: { : { : false } },
}),
)
return (
< ={}>
< />
</>
)
}
function () {
const [, ] = ("No application sent yet.")
const = ({
: (: ) =>
({ : ., : true }, 420),
})
const = ({
: (: ) =>
({ : `grant-${..}-2048` }, 520),
})
const = .(, {
,
async ({ }) {
try {
await .()
const = await .()
(`Application ${.} passed preview and was sent.`)
} catch {
("The review service is unavailable. Your values are intact.")
}
},
})
let =
if (.) = "Sending application…"
if (.) = "Building preview…"
const = ..()
let = null
if (.. === "registered") {
= (
<
={}
={..}
/>
)
}
let = "Individual"
if (. === "collective") = "Collective"
return (
<
="Research grant application example"
="form-please-complex"
>
< ="form-please-complex__kicker">Branching application</>
< ="form-please-complex__summary">
Applicant identity, registry lookup, settlement route, reporting rules,
and a two-request submission all share one typed form state.
</>
<. ="form-please-complex__form" ={}>
{}
<
="Application preview"
="form-please-complex__preview"
>
<>{.. || "Untitled application"}</>
<>
{}
{" · "}${...()} ·{" "}
{..} months
</>
</>
< ="form-please-complex__actions">
<. ="form-please-complex__primary">
Preview and send
</.>
< ="polite">{}</>
</>
</.>
</>
)
}
function <>(: , = 280): <> {
return new (() =>
.(() => (), ),
)
}Compare the other workflows in Examples.