Skip to content

Rent a car: Submitting the form

The Reserve button uses actionType: 'submit'. On click the form runs every validator; if all pass, <gui-form> dispatches a formSubmit event whose payload is a FormSubmitEvent carrying the collected form data. The host listens for that event — the form definition itself needs nothing more than the button shape.

The button stays as it was in the previous chapters — actionType: 'submit' is all the wiring you need on the widget side:

gui.actions.button({
label: 'Reserve',
actionType: 'submit',
}),

Validation gates submission for free: an invalid form simply surfaces the error messages from the validation chapter and never fires formSubmit.

Add a handleFormSubmit handler to <gui-form>’s formSubmit output, alongside the existing formEvent listener from the previous chapter. The handler receives a FormSubmitEvent whose .data is the collected form data. The snippets below import the JSON form file (./rent-a-car.form.json); if you’re using the programmatic API, swap the import path for ./rent-a-car.form (the .ts file):

rent-a-car.page.tsx
import { GuiForm } from '@golemui/gui-react';
import type { FormEvent, FormSubmitEvent } from '@golemui/core';
import { searchCars } from './car.service';
import rentACarForm from './rent-a-car.form.json';
import { CarItemRenderer } from './CarItemRenderer';
const config = {
formDef: rentACarForm,
// …itemRenderers (and `formConfig.states` if you're on the programmatic API) from earlier chapters…
};
async function handleFormEvent(event: FormEvent) {
// …loadCars / filterCars handling from the previous chapter…
}
function handleFormSubmit(event: FormSubmitEvent) {
console.log(event.data);
}
export function RentACarPage() {
return (
<GuiForm
config={config}
formEvent={handleFormEvent}
formSubmit={handleFormSubmit}
/>
);
}
06-final.ts
import { gui } from '@golemui/gui-shared';
export default [
gui.inputs.dropdown('car', {
labelField: 'label',
valueField: 'id',
itemRenderer: 'carItemRenderer',
inputDebounce: 300,
label: 'Select car',
validator: {
type: 'string',
required: true,
messages: {
required: 'Please pick a car model',
invalid: 'Please pick a car model',
},
},
onLoad: () => 'loadCars',
onFilter: () => 'filterCars',
}),
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,
messages: {
required: 'Choose where you\'ll pick up the car',
},
},
}),
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,
messages: {
required: 'Choose where you\'ll drop the car off',
},
},
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,
messages: {
required: 'Please select your rental dates',
minItems: 'Pick a rental date range',
maxItems: 'Only one date range, please',
},
},
}),
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,
messages: {
required: 'Choose Daily, Weekly, or Monthly',
},
},
}),
gui.inputs.booleanInput('driverOver25', {
label: 'Driver aged over 25',
validator: {
const: true,
required: true,
messages: {
const: 'Drivers must be at least 25 years old to rent',
required: 'Confirm the driver is over 25',
},
},
}),
gui.inputs.booleanInput('hasDiscountCode', {
label: 'I have a discount code',
}),
gui.inputs.textInput('discountCode', {
label: 'Discount code',
validator: {
required: true,
minLength: 4,
messages: {
required: 'Enter your discount code',
minLength: 'Discount codes are at least 4 characters',
},
},
include: {
in: ['hasDiscount'],
},
}),
gui.actions.button({
label: 'Reserve',
actionType: 'submit',
}),
];

Fill the form, click Reserve, and the collected form data arrives in your handleFormSubmit callback.

You’ve now built a real-world form that uses:

  • declarative widget composition with grid and flex layouts,
  • validators per field,
  • named states with include rules,
  • a custom item renderer,
  • async data loading with onLoad/onFilter and inputDebounce,
  • the form’s formSubmit event to capture submission and read the collected data.