Skip to content

Rent a car: States and conditional rendering

The form from the previous chapter renders every field unconditionally. We’ll now:

  1. Declare two statesdifferentReturn and hasDiscount — that mirror the matching toggles.
  2. Gate the optional fields with include: { in: [...] }.

Validators come next — they’re covered end-to-end in the Validation errors chapter.

States are named boolean expressions evaluated against $form. Where they live depends on which API you picked:

In the programmatic API, states live under formConfig.states — a top-level property of the same config object you wired to the form component in the previous chapter, right next to formDef:

rent-a-car.page.tsx
const config = {
formDef: rentACarForm,
formConfig: {
states: {
differentReturn: '$form.differentReturn === true',
hasDiscount: '$form.hasDiscountCode === true',
},
},
};

In the JSON API, states live at the form file’s root, alongside "form":

rent-a-car.form.json
{
"states": {
"differentReturn": "$form.differentReturn === true",
"hasDiscount": "$form.hasDiscountCode === true"
},
"form": [
// …widgets…
]
}

The page component stays exactly as it was in the intro chapterconfig just carries formDef:

rent-a-car.page.tsx
const config = { formDef: rentACarForm };

Add include: { in: ['<stateName>'] } to each optional widget — it will only render while the named state is active. In our form, that’s two widgets: the return-office dropdown (gated by differentReturn) and the discount-code input (gated by hasDiscount).

gui.inputs.dropdown('returnOffice', {
label: 'Return to office',
include: { in: ['differentReturn'] },
// …items, validator…
});
gui.inputs.textInput('discountCode', {
label: 'Discount code',
include: { in: ['hasDiscount'] },
validator: { required: true, minLength: 4 },
});
{
"kind": "input",
"type": "dropdown",
"path": "returnOffice",
"label": "Return to office",
"include": { "in": ["differentReturn"] }
}
{
"kind": "input",
"type": "textinput",
"path": "discountCode",
"label": "Discount code",
"include": { "in": ["hasDiscount"] },
"validator": { "required": true, "minLength": 4 }
}

The toggles that drive the states (differentReturn and hasDiscountCode) stay in the form unconditionally — they’re what the user interacts with to flip the state on or off.

02-states.ts
import { gui } from '@golemui/gui-shared';
export default [
gui.inputs.dropdown('car', {
labelField: 'label',
valueField: 'id',
items: [
{
id: 'compact',
label: 'Compact',
},
{
id: 'suv',
label: 'SUV',
},
{
id: 'convertible',
label: 'Convertible',
},
{
id: 'luxury',
label: 'Luxury',
},
],
label: 'Select car',
validator: {
type: 'string',
required: true,
},
}),
gui.layouts.grid([
gui.inputs.dropdown('collectOffice', {
labelField: 'label',
valueField: 'id',
items: [
{
id: 'lhr',
label: 'London Heathrow',
},
{
id: 'cdg',
label: 'Paris CDG',
},
{
id: 'fra',
label: 'Frankfurt Main',
},
],
label: 'Collect from office',
validator: {
type: 'string',
required: true,
},
}),
gui.inputs.dropdown('returnOffice', {
labelField: 'label',
valueField: 'id',
items: [
{
id: 'lhr',
label: 'London Heathrow',
},
{
id: 'cdg',
label: 'Paris CDG',
},
{
id: 'fra',
label: 'Frankfurt Main',
},
],
label: 'Return to office',
validator: {
type: 'string',
required: true,
},
include: {
in: ['differentReturn'],
},
}),
], {
direction: 'row',
autoFit: true,
}),
gui.inputs.booleanInput('differentReturn', {
label: 'Choose a different return location',
}),
gui.inputs.rangeCalendar('rentalDates', {
numberOfMonths: 2,
label: 'Rental dates',
validator: {
required: true,
minItems: 1,
maxItems: 1,
},
}),
gui.inputs.radiogroup('rentalType', {
options: [
{
label: 'Daily',
value: 'daily',
},
{
label: 'Weekly',
value: 'weekly',
},
{
label: 'Monthly',
value: 'monthly',
},
],
label: 'Rental type',
validator: {
type: 'string',
required: true,
},
}),
gui.inputs.booleanInput('driverOver25', {
label: 'Driver aged over 25',
validator: {
const: true,
required: true,
},
}),
gui.inputs.booleanInput('hasDiscountCode', {
label: 'I have a discount code',
}),
gui.inputs.textInput('discountCode', {
label: 'Discount code',
validator: {
required: true,
minLength: 4,
},
include: {
in: ['hasDiscount'],
},
}),
gui.actions.button({
label: 'Reserve',
actionType: 'submit',
}),
];
02-states.json
{
"states": {
"differentReturn": "$form.differentReturn === true",
"hasDiscount": "$form.hasDiscountCode === true"
},
"form": [
{
"kind": "input",
"type": "dropdown",
"path": "car",
"label": "Select car",
"validator": { "type": "string", "required": true },
"props": {
"labelField": "label",
"valueField": "id",
"items": [
{ "id": "compact", "label": "Compact" },
{ "id": "suv", "label": "SUV" },
{ "id": "convertible", "label": "Convertible" },
{ "id": "luxury", "label": "Luxury" }
]
}
},
{
"kind": "layout",
"type": "grid",
"props": { "direction": "row", "autoFit": true },
"children": [
{
"kind": "input",
"type": "dropdown",
"path": "collectOffice",
"label": "Collect from office",
"validator": { "type": "string", "required": true },
"props": {
"labelField": "label",
"valueField": "id",
"items": [
{ "id": "lhr", "label": "London Heathrow" },
{ "id": "cdg", "label": "Paris CDG" },
{ "id": "fra", "label": "Frankfurt Main" }
]
}
},
{
"kind": "input",
"type": "dropdown",
"path": "returnOffice",
"label": "Return to office",
"include": { "in": ["differentReturn"] },
"validator": { "type": "string", "required": true },
"props": {
"labelField": "label",
"valueField": "id",
"items": [
{ "id": "lhr", "label": "London Heathrow" },
{ "id": "cdg", "label": "Paris CDG" },
{ "id": "fra", "label": "Frankfurt Main" }
]
}
}
]
},
{
"kind": "input",
"type": "toggle",
"path": "differentReturn",
"label": "Choose a different return location"
},
{
"kind": "input",
"type": "rangeCalendar",
"path": "rentalDates",
"label": "Rental dates",
"props": { "numberOfMonths": 2 },
"validator": { "type": "array", "required": true, "minItems": 1, "maxItems": 1 }
},
{
"kind": "input",
"type": "radiogroup",
"path": "rentalType",
"label": "Rental type",
"validator": { "type": "string", "required": true },
"props": {
"options": [
{ "label": "Daily", "value": "daily" },
{ "label": "Weekly", "value": "weekly" },
{ "label": "Monthly", "value": "monthly" }
]
}
},
{
"kind": "input",
"type": "toggle",
"path": "driverOver25",
"label": "Driver aged over 25",
"validator": { "type": "boolean", "const": true, "required": true }
},
{
"kind": "input",
"type": "toggle",
"path": "hasDiscountCode",
"label": "I have a discount code"
},
{
"kind": "input",
"type": "textinput",
"path": "discountCode",
"label": "Discount code",
"include": { "in": ["hasDiscount"] },
"validator": { "type": "string", "required": true, "minLength": 4 }
},
{
"kind": "action",
"type": "button",
"label": "Reserve",
"actionType": "submit"
}
]
}
  • The Return to office dropdown only appears when Choose a different return location is on.
  • The Discount code input only appears when I have a discount code is on.

The embedded snippet already includes the validator on every required field — those are introduced in the next chapter.

Validation errors →