ausbizConsulting
Home/Components/MutableDialog

MutableDialog

A reusable dialog for handling both “Add” and “Edit” operations in one component.

React
TypeScript
React Hook Form
Zod

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

Complete customization
Code duplication
Inconsistent UI

Generic Dialog Per Page

Reduces duplication
Repetitive setup
Multiple props

MutableDialog Approach

Single reusable component
Type-safe with generics
Built-in validation

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:

PropTypeDescriptionRequired
formSchemaZodType<T>Zod schema for form validationYes
FormComponentReact.ComponentType<{ form: UseFormReturn<T> }>Component rendering the form fieldsYes
action(data: T) => Promise<ActionState<T>>Function to handle form submissionNo
defaultValuesDefaultValues<T>Initial values for edit modeNo
triggerButtonLabelstringText for the button that opens the dialogNo

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

AusBiz Consulting