Skip to content
Form, Please

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.

Applicant

Choose the legal path before entering dependent details.

Proposed work

At least 80 characters; this becomes the public summary.

Settlement and reporting

No application sent yet.

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.
  • useWatch for 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.