@playfast/forms-react
v0.0.5
Published
<div align="center">
Downloads
790
Readme
@playfast/forms-react
Typed JSX mapping for @playfast/forms.
Turn a typed form view into ergonomic helpers for your own React components — no bundled widgets, no global registry.
@playfast/forms-react does not own form state and does not ship DOM widgets. It maps a typed Form.view to JSX, keeping your layout fully custom while preventing it from drifting away from the schema.
Install
bun add @playfast/forms-react @playfast/forms @playfast/reform effect reactPeers: @playfast/forms, @playfast/reform, effect, and react (^19).
Usage
A composition provides a form view in its props:
const form = yield* Form.view(CheckoutForm)
return CheckoutContract.make({ form })The UI maps that view to JSX with FormUi.make:
import { FormUi } from '@playfast/forms-react'
export const CheckoutUi = FormUi.make(CheckoutContract, CheckoutForm, (
{ field, array, variant, form },
) => (
<Screen>
{field('email').as(EmailInput, { label: 'Email' })}
{array('items', ({ items, append }) => (
<Stack>
{items.map((item) => (
<Row key={item.key}>
{item.field('name').as(TextInput)}
{item.field('qty').as(QuantityStepper)}
<IconButton onPress={item.remove} />
</Row>
))}
<Button onPress={() => append({ name: '', qty: 1 })}>Add</Button>
</Stack>
))}
{variant('payment', {
Card: ({ field }) => (
<CardPanel>
{field('cardNumber').as(CardNumberInput)}
{field('cvv').as(CvvInput)}
</CardPanel>
),
Paypal: ({ field }) => (
<PaypalPanel>{field('email').as(EmailInput)}</PaypalPanel>
),
})}
<Button disabled={!form.canSubmit} onPress={form.submit}>Submit</Button>
</Screen>
))field('email').as(EmailInput) returns a ReactNode; the component receives a typed field prop:
const EmailInput = ({ field, label }: {
readonly field: FieldBinding<string>
readonly label: string
}) => (
<TextInput
label={label}
value={field.value}
error={Option.getOrUndefined(field.error)}
disabled={field.limitations.disabled}
onBlur={field.blur}
onChange={field.set}
/>
)Type safety
The helper types are derived from the form schema:
field(path)accepts only field paths and narrowsFieldBinding<A>.array(path)accepts only array paths and gives typed item scopes.variant(path, cases)accepts discriminated unions with_tagand requires a case for every tag.- Nested item and variant scopes expose the same helpers relative to that scope.
Platform boundary
This package is React-specific because it returns JSX, but it is not DOM-specific. Components can target DOM, React Native, Ink, or a custom host as long as they consume the FieldBinding and ArrayBinding shapes. There is intentionally no .dom, .widgets, or global component registry — import only the helper package and the components you actually render.
The reform family
Core @playfast/reform · form state @playfast/forms · hosts @playfast/react / @playfast/react-native · testing @playfast/proof
License
MIT
