Skip to content

v5.0.0-alpha.7

Pre-release
Pre-release

Choose a tag to compare

@danielweinmann danielweinmann released this 24 Mar 03:24
· 68 commits to main since this release

Breaking Changes

Replaced individual component props with a unified components prop

The 15 individual component props on SchemaForm (inputComponent, labelComponent, multilineComponent, selectComponent, checkboxComponent, radioComponent, checkboxWrapperComponent, radioWrapperComponent, radioGroupComponent, fieldErrorsComponent, fieldComponent, fieldsComponent, globalErrorsComponent, buttonComponent, and component) have been replaced by a single components prop that accepts a partial ComponentMap.

Before:

<SchemaForm
  schema={schema}
  component={StyledForm}
  inputComponent={MyInput}
  buttonComponent={MyButton}
  labelComponent={MyLabel}
  fieldsComponent={MyFieldsWrapper}
  globalErrorsComponent={MyErrors}
/>

After:

<SchemaForm
  schema={schema}
  components={{
    form: StyledForm,
    input: MyInput,
    button: MyButton,
    label: MyLabel,
    fields: MyFieldsWrapper,
    globalErrors: MyErrors,
  }}
/>

Every slot in ComponentMap is now constrained to a component that accepts the minimum props the library passes at runtime. Components with incompatible props fail at compile time instead of silently breaking at runtime.

The full set of slots is: form, field, label, input, multiline, select, checkbox, radio, checkboxWrapper, radioWrapper, radioGroup, fieldErrors, error, fields, globalErrors, button.

New makeSchemaForm factory

makeSchemaForm sets base components once for your entire app. The factory captures concrete types so that custom component props flow through to Field children with full type safety:

import { makeSchemaForm } from 'remix-forms'

const SchemaForm = makeSchemaForm({
  form: StyledForm,
  input: ChakraInput,
  multiline: ChakraTextarea,
  button: ChakraButton,
})

The returned component still accepts a components prop for per-form overrides. Components resolve in a 3-level cascade: per-form override > base (from makeSchemaForm) > built-in defaults.

Type-safe SmartInput

SmartInput now automatically knows which component it will render — derived from three sources — and accepts only that component's props:

  1. Schema typeboolean → Checkbox, enum → Select
  2. SchemaForm configmultiline={['bio']}, radio={['role']} (uses const type params for literal tuple inference)
  3. Field-level props<Field multiline>, <Field radio>
<SchemaForm schema={schema} multiline={['bio']} radio={['role']}>
  {({ Field }) => (
    <>
      {/* SmartInput infers Input slot → accepts ChakraInput's size/variant */}
      <Field name="email">
        {({ SmartInput }) => <SmartInput variant="filled" />}
      </Field>

      {/* 'bio' is in multiline config → SmartInput infers Multiline slot */}
      <Field name="bio">
        {({ SmartInput }) => <SmartInput resize="none" />}
      </Field>
    </>
  )}
</SchemaForm>

Bug fix

Fixed a pre-existing bug where the radio prop wasn't passed to Field via cloneElement when using custom children layouts, causing radio fields to never render as radio buttons in that scenario.

New Exports

  • makeSchemaForm — factory function for creating pre-configured SchemaForm components
  • ComponentMap (type) — describes all 16 component slots available for customisation
  • MergeComponents (type) — utility type for the 3-level component cascade

What's Changed

  • BREAKING: Replace individual component props with generic components prop by @danielweinmann in #402

Full Changelog: v5.0.0-alpha.6...v5.0.0-alpha.7