ReviewOS

stacks/stx

fix(components): `<Form>` declares initialValues / validateOnChange / validateOnBlur / onSubmit / onError and implements none of them

#1874
Closed glennmichael123 opened this 14 days ago · 1 comment
14 days ago

Summary

@stacksjs/components' <Form> publishes a prop surface it does not honour. FormProps and the runtime formPropSchema both declare initialValues, validateOnChange, validateOnBlur, onSubmit and onError; Form.stx reads none of them. FieldProps.touched is declared and never produced. FormHelpers promises setValues/resetForm; defineExpose ships setErrors/reset. Passing any of these is a silent no-op — prop validation accepts them, TypeScript accepts them, and nothing happens.

Impact (real app)

analyticshq validates only inside the submit handler, on every form, because there is no blur hook to hang anything on:

  • resources/views/register.stx:23-26 — password length is checked after the button is pressed and nowhere else.
  • resources/views/reset-password.stx:58-65 — you can type two mismatched passwords, tab away, and only learn on submit.
  • grep -rn "touched|dirty|pristine" resources/ returns nothing.

When we went looking for an existing primitive, FormProps.validateOnBlur and FieldProps.touched in the components package read as exactly the answer. They are not; they are declarations with no implementation behind them, which costs more than their absence would have.

Evidence in stx

  • packages/components/src/ui/form/index.ts:31-40 — FormProps declares initialValues, validateOnChange, validateOnBlur, onSubmit, onError.
  • packages/components/src/ui/form/index.ts:42-49 — FieldProps declares touched?: boolean.
  • packages/components/src/ui/form/index.ts:53-61 — formPropSchema registers initialValues: PropTypes.object, validateOnChange: PropTypes.boolean, validateOnBlur: PropTypes.boolean, onSubmit: PropTypes.func, onError: PropTypes.func, so prop validation actively accepts all five.
  • packages/components/src/ui/form/Form.stx:2-5 — the entire prop surface the component reads:
    export const action = $props.action || ''
    export const method = $props.method || 'POST'
    export const validationSchema = $props.validationSchema || {}
    export const className = $props.className || ''
    initialValues, validateOnChange, validateOnBlur, onSubmit and onError appear nowhere else in the file.
  • packages/components/src/ui/form/Form.stx:15-17 — the only state is errors, isSubmitting, submitCount. No touched, no dirty.
  • packages/components/src/ui/form/Form.stx:49-70 — onSubmit is a local handler bound to the <form>'s submit event; the declared onSubmit prop is never consulted, and the component emits 'submit'/'error' instead.
  • packages/components/src/ui/form/Form.stx:84 — defineExpose({ errors, isSubmitting, submitCount, setErrors, reset, validateForm }) vs FormHelpers at index.ts:23-27 (setErrors, setValues, resetForm). Two of three names differ and setValues does not exist.

Prior art

  • React Hook Form — formState.touchedFields, formState.isDirty, mode: 'onBlur' | 'onChange' | 'onSubmit'.
  • VeeValidate — meta.touched / meta.dirty per field.
  • Angular — ng-touched / ng-dirty / ng-pristine classes and control.touched.

Ask

  1. Implement the declared props in Form.stx: seed values from initialValues, track per-field touched/dirty, and re-validate on change/blur when validateOnChange/validateOnBlur are set.
  2. Reconcile defineExpose with FormHelpers — either rename to setValues/resetForm or correct the interface, and add whichever is missing.
  3. Honour or delete the onSubmit/onError props; today they are shadowed by the emit('submit')/emit('error') channel and a caller who passes them gets silence.
  4. Until (1) lands, remove the unimplemented entries from formPropSchema so prop validation stops accepting props the component ignores — a hard error is better than a no-op.
  5. useForm in @stacksjs/composables already implements this exact model (touched, dirty, validateOn: 'change'|'blur'|'submit', per-field errorId/inputProps()); <Form> should delegate to it rather than carry a second, weaker copy.

Related: stacksjs/stacks#1940

14 days ago

Claiming this one — working on it now.

Sign in to comment on this issue.