Skip to content

Complete form examples

Two forms end to end, assembling the pieces from the other pages: a flat creation form with validation and submission, then a nested one.

Read this after the overview — these examples don't introduce anything new, they show the parts fitting together.

Creation form with validation

ts
interface User {
  name: string;
  email: string;
  age: number;
}

const { createUserMutation } = mutation('createUserMutation', {
  method: (data: ValidatedFormValue<User>) => data,
  loader: function* ({ params: user }) {
    return yield* CraftHttpClient.update(({ response }) => ({
      url: '/api/users',
      body: user,
      success: response<User>(),
    }));
  },
});

const { userFormState } = state(
  'userFormState',
  { name: '', email: '', age: 0 } satisfies User,
  insertForm(
    insertSelectFormTree(
      'name',
      insertNoopTypingAnchor,
      insertFormAttributes(() => ({
        validators: [cRequired()],
      })),
    ),
    insertSelectFormTree(
      'email',
      insertNoopTypingAnchor,
      insertFormAttributes(() => ({
        validators: [cRequired(), cEmail()],
      })),
    ),
    insertSelectFormTree(
      'age',
      insertNoopTypingAnchor,
      insertFormAttributes(() => ({
        validators: [cMin({ min: 18 })],
      })),
    ),
    insertFormSubmit(createUserMutation),
  ),
);

Complex Nested Form

When a selected branch needs more than two insertions, compose them with craftPipe. The common two-insertion case (insertNoopTypingAnchor followed by insertFormAttributes) can be passed directly as the second argument.

ts
interface Address {
  street: string;
  city: string;
  zipCode: string;
}

interface User {
  name: string;
  email: string;
  addresses: Address[];
}

const { userFormState } = state(
  'userFormState',
  {
    name: '',
    email: '',
    addresses: [],
  } satisfies User,
  insertForm(
    insertSelectFormTree(
      'name',
      insertNoopTypingAnchor,
      insertFormAttributes(() => ({
        validators: [cRequired()],
      })),
    ),
    insertSelectFormTree(
      'email',
      insertNoopTypingAnchor,
      insertFormAttributes(() => ({
        validators: [cRequired(), cEmail()],
      })),
    ),
    insertSelectFormTree(
      'addresses',
      (context) =>
        craftPipe(
          context,
          insertNoopTypingAnchor,
          insertSelectFormTree(
            'street',
            insertNoopTypingAnchor,
            insertFormAttributes(() => ({
              validators: [cRequired()],
            })),
          ),
          insertSelectFormTree(
            'city',
            insertNoopTypingAnchor,
            insertFormAttributes(() => ({
              validators: [cRequired()],
            })),
          ),
          insertSelectFormTree(
            'zipCode',
            insertNoopTypingAnchor,
            insertFormAttributes(() => ({
              validators: [cRequired(), cPattern({ pattern: /^\d{5}$/ })],
            })),
          ),
        ),
    ),
    insertSelectFormTree(
      'address',
      insertNoopTypingAnchor,
      insertFormAttributes(() => ({
        validators: [cRequired(), cMinLength({ minLength: 5 })],
      })),
    ),
  ),
);

See Also