Frontend Forms (React Hook Form)
This page shows how I build forms in the frontend usingreact-hook-form with MUI components, and how I surface validation and submission errors.
How it works
Here’s the pattern I follow:1) useForm for data + validation
I start with a typed form model and useForm({ defaultValues }).
Example: apps/portal/src/components/memberForm.tsx uses register, handleSubmit, and formState.errors:
2) Controller when the MUI input needs controlled wiring
When the component isn’t a simple ref-friendly input, I use Controller.
Example: apps/portal/src/pages/admin/settings/billing/checkout/newPaymentForm/customerInfo/form.tsx uses Controller with rules={{ required: true }}.
3) Submission uses generated mutation hooks
On submit, the component calls the generated mutation and then renders a user-facing result. Example: billing customer updates read the mutation error and success directly:4) Keep user-facing messages centralized
For checkout-related UX, I reuse constants from:apps/portal/src/utils/checkout.ts (example: UNEXPECTED_ERROR_MESSAGE).
Extending this system
Here’s how I extend the frontend form pattern when you need a new screen or input set.- Define the form data type and
defaultValues.
apps/portal/src/components/memberForm.tsx (MemberForm).
- Wire RHF validation rules to each input.
helperText={errors.field?.message} for MUI TextField.
-
Use
Controllerwhen RHF’sregisterwon’t work cleanly with the MUI component. - Call a generated mutation hook in the submit handler.
- Verify:
What not to do
If you skip these, your form UX will stay consistent across the app. ❌ Never bypass generated hooks and call backend endpoints manually from React. ❌ Never duplicate checkout error strings whenapps/portal/src/utils/checkout.ts already has them.
❌ Never implement complex business rules (entitlements, scopes, billing math) in the form component.
File conventions
This is where new form code should live, based on how the existing screens are organized. Prefer:apps/portal/src/components/memberForm.tsxstyle for reusable form componentsapps/portal/src/pages/.../form.tsxstyle for screen-specific forms
What’s next?
When your form touches a failure path, the next doc shows how error UX stays user-friendly.- Error handling UX for how mutation failures turn into UI states.
- Frontend GraphQL Layer for the generated hook contract these forms rely on.
