Skip to content

Nested forms

insertSelectFormTree targets a branch of the form, and insertSubFormField declares a sub-form inside it — for state that is not flat.

Use them when the state has nested objects or arrays of objects. Not when the form is one level deep — attach insertFormAttributes directly.

insertSelectFormTree

Selects and composes nested sub-forms.

ts
interface ProductForm {
  name: string;
  variants: Array<{
    color: string;
    stock: number;
  }>;
}

const { productFormState } = state(
  'productFormState',
  { name: '', variants: [] } as ProductForm,
  insertForm(
    insertSelectFormTree(
      'variant',
      insertNoopTypingAnchor,
      insertFormAttributes(() => ({
        validators: [cRequired(), cMin({ min: 0 })],
      })),
    ),
  ),
);

// Access sub-forms
const form = productFormState.form();
const variant0 = form.selectVariant(0);
const allVariants = form.items();

insertSubFormField

Exposes a derived sub-form from a parent value through a lens. This is useful when the form field is not stored as a nested object in the state, but can still be read and written from the parent value.

ts
import { state } from '@craft-ng/core';
import {
  insertForm,
  insertFormAttributes,
  insertSubFormField,
  splitLens,
  cRequired,
} from '@craft-ng/core';

const { appointmentFormState } = state(
  'appointmentFormState',
  '2026-05-10 12:00',
  insertForm(
    insertSubFormField(
      'date',
      splitLens(' ', 0),
      insertFormAttributes(() => ({
        validators: [cRequired()],
      })),
    ),
    insertSubFormField('time', splitLens(' ', 1)),
  ),
);

const form = appointmentFormState.form();
const dateField = form.selectDate();
const timeField = form.selectTime();

console.log(dateField.value()); // '2026-05-10'
console.log(timeField.value()); // '12:00'

dateField.set('2026-05-11');
timeField.set('09:30');

console.log(appointmentFormState()); // '2026-05-11 09:30'

See Also