Dynamic Values
Reference other field values or external context in labels, defaults, and validator args.
Dynamic References (Expressions)
BuzzForm supports dynamic references in field properties using { $data: path } and { $context: path } syntax. These are powered by the unified Expression system (Expr<T>).
Expression Types
An expression can be a literal value, a reference, or complex logic:
type Expr<T> =
| T // Literal (string, number, boolean)
| { $data: string } // Reference form data
| { $context: string } // Reference external context
| { $args: string } // Reference dynamic arguments (e.g. validator args)
| { $text: string } // Template interpolation
| { $when: Condition; $then: Expr<T>; $else: Expr<T> } // Ternary logic
| { $fn: string; args?: Record<string, Expr<any>> } // Registry function
| ((ctx: ExprContext) => T); // Inline JS functionThis works for:
ExprString— labels, descriptions, placeholdersExprNumber— validator args, min/max valuesExprBoolean—disabled,required,condition
Advanced Expressions
Unified expressions support powerful logic nodes that minimize the need for custom code.
Template Interpolation ($text)
Build dynamic strings using ${/path} syntax. References are resolved from form data:
{
type: "text",
name: "confirmation",
// "Hello John, please confirm your email."
label: { $text: "Hello ${/firstName}, please confirm your email." },
}Validator Argument Interpolation
Inside validation messages, you can reference the resolved arguments of the validator using the /args/ prefix:
{
type: "text",
name: "username",
validate: {
onBlur: {
checks: [
{
type: "minLength",
// "Must be at least 10 characters (you have 5)."
message: { $text: "Must be at least ${/args/min} characters (you have ${/username})." },
args: { min: 10 },
},
],
},
},
}Conditional Branching ($when)
Return different values based on a boolean condition:
{
type: "text",
name: "discountCode",
// Label changes based on membership status
label: {
$when: { $data: "/isVip", eq: true },
$then: "Enter VIP Code",
$else: "Enter Promo Code"
}
}Registry Functions ($fn)
Call custom functions registered in registries.fns. Arguments themselves can be expressions:
// 1. Register function
<FormProvider registries={{
fns: {
calculateTotal: ({ args }) => (args.price as number) * 1.1
}
}}>
// 2. Call in schema
{
type: "number",
name: "totalPrice",
defaultValue: {
$fn: "calculateTotal",
args: { price: { $data: "/basePrice" } }
}
}Inline Functions
For schemas defined in JavaScript (not JSON), you can use standard functions. This is the ultimate escape hatch:
{
type: "text",
name: "manager",
// JS logic with full context access
condition: ({ data, context }) => {
return data.role === "Manager" && context.isEnterprise;
}
}Using $data
Reference other field values in your form:
In Labels and Descriptions
const contactSchema = defineSchema({
fields: [
{
type: "text",
name: "companyName",
label: "Company Name",
required: true,
},
{
type: "text",
name: "confirmationMessage",
// Label shows the company name dynamically
label: { $data: "/companyName" },
description: "Confirm your registration for: { $data: /companyName }",
placeholder: `Enter confirmation for { $data: "/companyName" }`,
},
],
});In Default Values
const profileSchema = defineSchema({
fields: [
{ type: "text", name: "firstName", label: "First Name" },
{ type: "text", name: "lastName", label: "Last Name" },
{
type: "text",
name: "fullName",
label: "Full Name",
// Mirror firstName as default (note: this doesn't concatenate)
defaultValue: { $data: "/firstName" },
},
],
});In Validator Args
const customFormSchema = defineSchema({
fields: [
{
type: "text",
name: "minChars",
label: "Minimum Characters",
defaultValue: 5,
},
{
type: "text",
name: "username",
label: "Username",
validate: {
onBlur: {
checks: [
{
type: "minLength",
message: "Username too short",
// Use value from another field
args: { min: { $data: "/minChars" } },
},
],
},
},
},
],
});Using $context
Reference external data passed via contextData:
const form = useForm({
schema: contactSchema,
contextData: {
userRole: "admin",
maxUploadSize: 10,
allowedDomains: ["company.com", "partner.com"],
supportEmail: "support@example.com",
},
});
const contactSchema = defineSchema({
fields: [
{
type: "email",
name: "workEmail",
label: "Work Email",
validate: {
onBlur: {
checks: [
{
type: "validateEmailDomain",
message: "Please use a company email",
args: {
allowedDomains: { $context: "/allowedDomains" },
maxSize: { $context: "/maxUploadSize" },
},
},
],
},
},
},
{
type: "text",
name: "adminNotes",
label: "Admin Notes",
// Only shown for admin users
condition: { $context: "/userRole", eq: "admin" },
},
{
type: "text",
name: "supportContact",
label: "Support Contact",
// Display dynamic support email
description: { $context: "/supportEmail" },
},
],
});Path Format
BuzzForm uses JSON Pointer format (RFC 6901):
| Path Type | Example | Description |
|---|---|---|
| Root field | /username | Field at root level |
| Nested field | /address/city | Field in a group |
| Array item | /items/0/name | Specific array item |
| Context data | /user/role | Context data path |
Important:
- Paths must start with
/ - Use
/as separator (not.) - Array indices are numeric (
/items/0)
resolveExpr Helper
Resolve dynamic values manually:
import { resolveExpr } from "@buildnbuzz/form-react";
const ctx = {
data: { name: "John", age: 30 },
context: { maxAge: 100 }
};
// Resolve a $data reference
resolveExpr<string>({ $data: "/name" }, ctx);
// "John"
// Resolve a $context reference
resolveExpr<number>({ $context: "/maxAge" }, ctx);
// 100
// Literal values pass through unchanged
resolveExpr("hello", ctx);
// "hello"Examples
Dynamic Field Options
const contactSchema = defineSchema({
fields: [
{
type: "select",
name: "country",
label: "Country",
options: ["US", "UK", "Canada"],
},
{
type: "select",
name: "region",
label: "State / Province",
options: [
{ label: "California", value: "CA" },
{ label: "New York", value: "NY" },
// Disable options dynamically
{ label: "Texas", value: "TX", disabled: { $data: "/country", neq: "US" } },
],
},
],
});Dynamic Validation Thresholds
const applicationSchema = defineSchema({
fields: [
{
type: "number",
name: "experienceLevel",
label: "Experience Level (1-10)",
min: 1,
max: 10,
},
{
type: "text",
name: "justification",
label: "Justification",
// Required only for high experience
required: { $data: "/experienceLevel", gte: 7 },
validate: {
onSubmit: {
checks: [
{
type: "minLength",
message: "High experience requires detailed justification",
// Longer min length for higher experience
args: {
min: { $data: "/experienceLevel" }, // min = experienceLevel value
},
},
],
},
},
},
],
});Dynamic Collapsible State
const settingsSchema = defineSchema({
fields: [
{
type: "checkbox",
name: "showAdvanced",
label: "Show Advanced Options",
},
{
type: "collapsible",
label: "Advanced Settings",
// Collapse/expand based on checkbox
collapsed: { $data: "/showAdvanced", eq: false },
fields: [
// ... advanced fields
],
},
],
});Limitations
Not supported:
- ❌ Relative paths like
../sibling(use absolute/sibling) - ❌ Dot notation like
user.name(use/user/name)
Supported:
- ✅ In
messagestrings (Interpolation & logic) - ✅ In
argsobject of validation checks - ✅ In labels, descriptions, placeholders
- ✅ In defaultValue
- ✅ In boolean props (disabled, required, collapsed)
- ✅ In validator args
Dynamic Options
For static option lists with dynamic disabled values, use Condition:
{
type: "select",
name: "state",
options: [
{ label: "California", value: "CA" },
{ label: "Texas", value: "TX", disabled: { $data: "/country", neq: "US" } },
],
}For async option loading (REST APIs, GraphQL, etc.), use the option resolver registry instead. This provides automatic dependency tracking, request deduplication, and cascading dropdown support:
const resolvers = defineOptionResolvers({
listStates: async ({ data }) => {
const res = await fetch(`/api/states?country=${data.country}`);
const json = await res.json();
return json.states.map((s: State) => ({ label: s.name, value: s.code }));
},
});
// Schema
{
type: "select",
name: "state",
options: { resolver: "listStates" },
dependencies: ["/country"], // auto re-fetches when country changes
}See Option Resolvers for the full guide.
Related
- Conditional Logic — Use
$datain conditions - Validation — Use
$datain validator args - Schema — Define fields with dynamic values