Search KetJS

Search titles, descriptions, and section headings.

    Diagram

    100%
    Drag to pan · Scroll or pinch to zoom · + / − to zoom · Arrow keys to pan · 0 to fit · 1 to reset
    ← All lessons

    STEP 06 / Build the frontend

    Validate a form with a shared schema

    Cast and validate form values, present useful errors, and retain the server validation boundary.

    Course outline · lesson 6 of 33

    Before you start: complete Grow the counter into a todo list, or make sure you can pass its checkpoint.

    Try the validation preset#

    Select Form · shared validation in the playground. Submit an empty email, an invalid string, and then learner@example.com. The live status should explain the result without reloading the page.

    The reference island uses a schema that is safe to share between browser and server:

    // File: learn-view/src/islands/validation.tsx
    import { signal, defineFormSchema, validateForm } from '@ketvietlab/ketjs-view'
    
    export default function Signup(_props: Record<string, unknown>) {
      const schema = defineFormSchema({ fields: { email: { type: 'text', required: true, trim: true, pattern: /^[^@]+@[^@]+$/ } }, unknown: 'reject' })
      const message = signal('Enter an email address.')
      return () => <form onSubmit={(event: Event) => {
        event.preventDefault()
        const form = event.currentTarget as HTMLFormElement
        const result = validateForm(schema, { email: new FormData(form).get('email') })
        message.set(result.valid ? 'Valid input. The server must validate it again.' : 'Please enter a valid email.')
      }}>
        <h1>Validate a form</h1>
        <label for="email">Email</label>
        <input id="email" name="email" type="text" />
        <button type="submit">Check input</button>
        <p role="status">{message()}</p>
      </form>
    }

    Read the schema as a contract#

    required rejects an absent value. trim normalizes surrounding spaces. pattern applies a small exercise-specific check; this example is not a complete email deliverability validator. unknown: 'reject' makes unexpected fields visible instead of silently accepting them.

    A native form sends strings, repeated values, and missing controls. Use the schema to normalize those inputs before consuming them. For a larger form, valuesFromFormData() preserves repeated names, and validateForm() exposes field errors and form errors separately.

    Preserve useful browser behavior#

    The submit handler prevents navigation only after the browser enhancement owns the flow. Labels remain associated with controls. A status region announces feedback. Keep the user's input after an error so they can correct it.

    For a production form with a server action, retain a native action/method fallback and validate again in the server boundary. A user can call the endpoint directly or modify the page; browser validation is assistance, not permission to write data.

    Add a second field#

    Add a display name with a maximum length. Put the error beside its input rather than returning only a generic failure. When moving the schema into a shared file, import its browser-safe contract from ketjs-view and avoid importing backend modules into the browser bundle.

    Checkpoint#

    Invalid input stays on screen, valid input produces normalized values, and you can explain why the server still needs validation.

    Practice on your own#

    Add a confirmation field and a cross-field check. Verify that correcting one field clears the corresponding error without clearing unrelated draft values.

    Reference#

    For the complete API contract, read Form Validation.