MutableDialog
A reusable dialog for handling both “Add” and “Edit” operations in one component.
Overview
In most applications, managing "Add" and "Edit" operations is a fundamental requirement. Creating separate dialogs for each entity—users, products, orders—might seem like the simplest approach, but this approach becomes increasingly difficult to maintain as your application grows.
MutableDialog addresses these challenges by offering a structured way to handle both operations within a single, reusable dialog component that integrates seamlessly with React Hook Form and Zod for validation.
Separate Add & Edit Dialogs
Generic Dialog Per Page
MutableDialog Approach
Installation
Install the component and its dependencies:
npm install react-hook-form zod @hookform/resolvers sonner
Create the MutableDialog component:
MutableDialog determines the mode based on whether “Add” or “Edit” operations are needed through a single component interface.
"use client"
import type React from "react"
import { useState, useEffect } from "react"
import { useForm, type UseFormReturn, type FieldValues, type DefaultValues } from "react-hook-form"
import { Button } from "@/components/ui/button"
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog"
import { toast } from "sonner"
import type { ZodType } from "zod"
export interface ActionState<T> {
success: boolean
message: string | null
data?: T
}
Usage
MutableDialog handles both “Add” and “Edit” operations through a single component interface. It determines the mode based on whether default values are provided or not.
import MutableDialog from '@/components/mutable-dialog';
import { userSchema } from './schemas';
import UserForm from './UserForm';
import { handleAddUser } from './actions';
export function UserDialog() {
return (
<MutableDialog
formSchema={userSchema}
FormComponent={UserForm}
action={handleAddUser}
triggerButtonLabel="Add User"
addDialogTitle="Add New User"
dialogDescription="Fill out the form below to add a new user."
submitButtonLabel="Save"
/>
);
}
API Reference
MutableDialog accepts the following props:
Prop | Type | Description | Required |
---|---|---|---|
formSchema | ZodType<T> | Zod schema for form validation | Yes |
FormComponent | React.ComponentType<{ form: UseFormReturn<T> }> | Component rendering the form fields | Yes |
action | (data: T) => Promise<ActionState<T>> | Function to handle form submission | No |
defaultValues | DefaultValues<T> | Initial values for edit mode | No |
triggerButtonLabel | string | Text for the button that opens the dialog | No |
Examples
Basic Example
import { z } from 'zod';
import MutableDialog from '@/components/mutable-dialog';
// 1. Define your schema
const userSchema = z.object({
name: z.string().min(1, 'Name is required'),
email: z.string().email('Invalid email'),
});
// 2. Create your form component
function UserForm({ form }) {
const { register, formState: { errors } } = form;
return (
<div className="space-y-4">
<div>
<label htmlFor="name">Name</label>
<input id="name" {...register("name")} />
{errors.name && <p>{errors.name.message}</p>}
</div>
<div>
<label htmlFor="email">Email</label>
<input id="email" type="email" {...register("email")} />
{errors.email && <p>{errors.email.message}</p>}
</div>
</div>
);
}
// 3. Define your action handler
async function handleAddUser(data) {
try {
// Call your API here
return {
success: true,
message: `User ${data.name} added successfully`,
};
} catch (error) {
return {
success: false,
message: 'Failed to add user',
};
}
}
// 4. Use the MutableDialog component
export function AddUserDialog() {
return (
<MutableDialog
formSchema={userSchema}
FormComponent={UserForm}
action={handleAddUser}
triggerButtonLabel="Add User"
/>
);
}
Ready to streamline your form handling?
Try MutableDialog today and reduce your form-handling boilerplate