Introduction

In this article I will show the full implementation of all form components.


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.tsx

Card

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.