diff --git a/docs/.vitepress/config.ts b/docs/.vitepress/config.ts index db4a737..dbd8a29 100644 --- a/docs/.vitepress/config.ts +++ b/docs/.vitepress/config.ts @@ -27,6 +27,7 @@ export default defineConfig({ { text: 'Async Step Validation', link: '/guide/async-validation' }, { text: 'Error Handling', link: '/guide/errors' }, { text: 'Custom Actions & Progress', link: '/guide/custom-components' }, + { text: 'Inline Edit Pattern', link: '/guide/inline-edit' }, { text: 'Examples', link: '/guide/examples' }, ] }, diff --git a/docs/guide/inline-edit.md b/docs/guide/inline-edit.md new file mode 100644 index 0000000..536eed5 --- /dev/null +++ b/docs/guide/inline-edit.md @@ -0,0 +1,106 @@ +# Inline Edit / Per-Field Save + +Account settings pages save each field independently — change your display name, hit Save next to it, that single field updates. `FormRenderer` assumes one submit at the end, but it composes well into this pattern: **one `FormRenderer` per row**. + +## The pattern + +Each editable row is its own form. One field, one validation cycle, one submit handler. + +::: code-group +```tsx [React] +import { FormRenderer } from '@formhaus/react'; + +function DisplayNameRow({ user, onSave }) { + return ( + onSave({ name: values.name })} + /> + ); +} +``` + +```vue [Vue] + + + +``` +::: + +You get validation, error display, async submit handling, and analytics events for free — same as a full multi-field form. + +## Inline button layout + +If the default actions footer is too heavy for a settings row, swap it via `ActionsComponent` (React) or `actionsComponent` (Vue): + +::: code-group +```tsx [React] +import type { FormActionsProps } from '@formhaus/react'; +import Button from '@mui/material/Button'; + +function InlineSaveActions({ onSubmit, loading }: FormActionsProps) { + return ( + + ); +} + +; +``` +::: + +The renderer wires the button to the engine: validation runs first, the button shows loading while `onSubmit` resolves, errors appear next to the field. You write the button. The engine handles the rest. + +## Why not one big form? + +A single `FormRenderer` with N fields validates and submits all-or-nothing. Settings pages need each field to save independently — display name failing shouldn't block the email change two rows down. One `FormRenderer` per row keeps the model simple: each row is a small autonomous form. + +## Tradeoffs + +- **N engine instances.** Each `FormRenderer` creates its own `FormEngine`. For 10 rows that's 10 instances, each with one field. Cheap, but not free. If you have 50+ rows in a single page, profile. +- **No cross-row state.** Each row is independent. If field B should clear when field A changes, you need a parent component holding shared state and passing it via `initialValues`. +- **Repeated submit boilerplate.** Each row writes its own `onSubmit`. A small wrapper component that takes `field`, `initialValue`, and an API call helps. + +For account-settings-style pages this pattern is the right shape. For one-shot wizards or checkout, use a single `FormRenderer` with all fields.