@payfit/unity-components
Version:
103 lines (85 loc) • 2.99 kB
Markdown
# Unity TanStack Form patterns
### Composed API (default)
Drop a single bound field component inside `<form.AppField>`. It bundles label, input, helper text, feedback, and a11y wiring.
```tsx
<form.AppField name="firstName">
{field => (
<field.TextField
label="First name"
helperText="As it appears on your ID"
isRequired
/>
)}
</form.AppField>
<form.AppField name="country">
{field => (
<field.SelectField
label="Country"
options={[
{ value: 'fr', label: 'France' },
{ value: 'es', label: 'Spain' },
]}
/>
)}
</form.AppField>
```
### Atomic API (only when customizing layout/parts)
Reach for Atomic when you need to interleave custom content between the label and the input, or swap an input for a non-standard control. Wraps every part in `field.Field`.
```tsx
<form.AppField name="password">
{field => (
<field.Field>
<field.FieldLabel isRequired>Password</field.FieldLabel>
<field.FieldHelperText>Enter a strong password</field.FieldHelperText>
<field.TextInput type="password" />
<form.Subscribe selector={s => s.values.password}>
{password => (
<Text variant="bodySmallStrong">Length: {password.length}</Text>
)}
</form.Subscribe>
<field.FieldFeedbackText />
</field.Field>
)}
</form.AppField>
```
### Validation timing
`validators.onBlur` is the default; use `onChange` only for fields that need live feedback (password strength meter, search-as-you-type). `fieldRevalidateLogic` gives "blur until first error, then change" UX without polluting form-level validators.
```tsx
import { fieldRevalidateLogic, useTanstackUnityForm } from '@payfit/unity-components'
const form = useTanstackUnityForm({
defaultValues: { email: '', password: '' },
validators: { onBlur: z.object({ email: z.email() }) },
validationLogic: fieldRevalidateLogic({
whenPristine: 'blur',
whenDirty: 'change',
fields: ['password'],
}),
})
<form.AppField
name="password"
validators={{
onDynamic: ({ value }) =>
value.length < 8 ? 'Password too short' : undefined,
}}
>
{field => <field.PasswordField label="Password" />}
</form.AppField>
```
For fields listed in `fieldRevalidateLogic.fields`, use
`onDynamic`/`onDynamicAsync` as the sole validator and omit those fields from
the form-level schema; combining both leaves stale errors.
### Optimal subscription with form.Subscribe + selector
Pass a `selector` to `form.Subscribe` so it observes only the state needed by
its children. A bare subscription re-renders on every form update.
```tsx
<form.Subscribe selector={s => s.values.password}>
{password => <Text>Length: {password.length}</Text>}
</form.Subscribe>
<form.Subscribe selector={s => [s.canSubmit, s.isSubmitting] as const}>
{([canSubmit, isSubmitting]) => (
<Button type="submit" isDisabled={!canSubmit} isLoading={isSubmitting}>
Submit
</Button>
)}
</form.Subscribe>
```