Rent a car: States and conditional rendering
The form from the previous chapter renders every field unconditionally. We’ll now:
- Declare two states —
differentReturnandhasDiscount— that mirror the matching toggles. - Gate the optional fields with
include: { in: [...] }. - Add validators to every required field so the form’s health goes from
errored→okonly when all validators pass.
Declaring states
Section titled “Declaring states”States are named boolean expressions evaluated against $form. Pass them as the second arg of the formConfig:
const formConfig = { states: { differentReturn: '$form.differentReturn === true', hasDiscount: '$form.hasDiscountCode === true', },};JSON forms put the same map at the form root under "states".
Gating fields with include
Section titled “Gating fields with include”Add include: { in: ['<stateName>'] } to the optional widgets — they’ll only render while the named state is active.
Result
Section titled “Result”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.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.booleanInput('differentReturn', { label: 'Choose a different return location', }), 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, }, }), gui.inputs.rangeCalendar('rentalDates', { label: 'Rental dates', validator: { type: 'array', required: true, minItems: 2, maxItems: 2, }, }), 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: { type: 'boolean', const: true, required: true, }, }), gui.inputs.booleanInput('hasDiscountCode', { label: 'I have a discount code', }), gui.inputs.textInput('discountCode', { label: 'Discount code', validator: { type: 'string', required: true, minLength: 4, }, }), gui.actions.button({ label: 'Reserve', uid: 'submit', onClick: 'submit', }),];{ "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": "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": "toggle", "path": "differentReturn", "label": "Choose a different return location" }, { "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": "rangeCalendar", "path": "rentalDates", "label": "Rental dates", "validator": { "type": "array", "required": true, "minItems": 2, "maxItems": 2 } }, { "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", "uid": "submit", "on": { "click": "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.
Disabling submit until valid
Section titled “Disabling submit until valid”GolemUI emits a formHealth callback whenever the form’s validation status changes. You can wire it to a piece of UI state and conditionally disable your submit handler — or use the disabled prop on the button with a when expression that references $form.<paths>.
The example above uses the simpler 'submit' literal on the button: onClick: 'submit' triggers form-level validation before the event fires, so an invalid form won’t fire the event at all. See Submitting the form for the full submit wiring.
See also
Section titled “See also”- Features / States — gating, properties, composing.
- Form Definition / Conditionals —
include/exclude/whenreference.