Introduction
In this article I will show the full implementation of all form components.
| Overview |
|---|
| FolderStructure |
| Card |
| ActionState |
| TicketUpsertForm |
| SubmitButton |
| FieldError |
| UpsertTicket |
| Zod Validation |
| Form |
| useActionFeedback |
| Toast Message |
FolderStructure
Implementing a form, will need a few components. One way to organize them, is shown in the diagram below:
src/
βββ app/
βββ components/
β βββ form/
β βββ hooks/
β β βββ use-action-feedback.ts
β βββ utils/
β β βββ to-action-state.ts
β βββ field-error.tsx
β βββ form.tsx
β βββ submit-button.tsx
βββ features/
βββ ticket/
βββ actions/
β βββ upsert-ticket.ts
βββ components/
βββ ticket-upsert-form.tsxCard
For sharing UI across forms.
Having an UI that is consistent across the whole application should always be a goal when developing. Thats why I am going to start with creating a Card Component, which I then can reuse later for different kind of forms that I can then pass down as a parameter. This card is technically not part of the form components but it acts as a wrapper component for a consistent UI.
Since this Card Component can be used in different applications, I am going to place it to:
src/components/card-compact.tsx| Details |
|---|
| π¨π½βπ» Card Component |
ActionState
Messenger between form submit and components
src/components/form/utils/to-action-state.ts| Details |
|---|
| π¨π½βπ» to-action-state.ts |
| π ActionState explained |
TicketUpsertForm
Custom form for or update or create a ticket
src/features/ticket/components/ticket-upsert-form.tsx| Details |
|---|
| π¨π½βπ» ticket-upsert-from.tsx |
| π TicketUpsertForm explained |
SubmitButton
src/components/form/submit-button.tsx| Details |
|---|
| π¨π½βπ» submit-button.tsx |
| π SubmitButton explained |
FieldError
Message that shows the zod validation issue to the user
src/components/form/field-error.tsx| Details |
|---|
| π¨π½βπ» field-error.tsx |
| π FieldError |
UpsertTicket
The function that gets called when submitted which will try to update the database
src/features/ticket/actions/upsert-ticket.ts| Details |
|---|
| π¨π½βπ» upsert-ticket.ts |
| π UpsertTicket explained |
Zod Validation
With zod I can easily check if the user input matches my criteriaβs. Itβs important to mention that the zod schema needs to be synced with the prisma schema.
Example of a zod schema
const upsertTicketSchema = z.object({
title: z.string().min(1, "title can't be empty").max(191),
content: z.string().min(1).max(1024),
});The corresponding prisma schema content variable
model Ticket {
content String @db.VarChar(1024)
}And the parse attempt that might throw an error
try {
const data = upsertTicketSchema.parse({
title: formData.get("title"),
content: formData.get("content"),
});
} catch (error) {
return fromErrorToActionState(error, formData);
}Form
For sharing logic across forms.
All forms across the whole application should not only share the same UI but also the same behavior. Whenever the submitting fails or succeeds, the user should be notified via a toaster message. Since I donβt want to implement the logic for the toaster in every single form, I will abstract it into a reusable form component.
src/components/form/form.tsx| Details |
|---|
| π¨π½βπ» form.tsx |
| π Form explained |
useActionFeedback
src/components/form/hooks/use-action-feedback.ts| Details |
|---|
| π¨π½βπ» use-action-feedback.tsx |
| π useActionFeedback explained |
Toast Message
The toaster message can be installed via shadcn
And should be placed in the root layout.tsx
<ThemeProvider>
<Header></Header>
<main>
{children}
</main>
<Toaster expand />
</ThemeProvider>Note: The expand parameter will allow the toaster messages to be displayed on top of each other instead of being stacked behind each other.