Hooks Reference

Complete reference for all Forms fragment hooks.

Overview

The Forms fragment provides hooks for both public form operations and administrative management. All hooks are created via the createFormsClient() function.

import { formsClient } from "@/lib/forms-client";

// Query hooks return { data, loading, error }
const { data, loading, error } = formsClient.useForm({ path: { slug: "contact" } });

// Mutation hooks return { mutate, loading, error }
const { mutate, loading, error } = formsClient.useCreateForm();

Public Hooks

These hooks are intended for end-users to view and submit forms.

RouteDescriptionHook
GET /:slugFetch a form by sluguseForm
POST /:slug/submitSubmit a responseuseSubmitForm

useForm

Fetches a form definition by its slug. Returns the form's data schema, UI schema, and metadata.

Parameters:

NameTypeRequiredDescription
path.slugstringYesThe form's URL-friendly identifier

Returns: Form

Prop

Type

Error Codes: NOT_FOUND

Example:

function FormDisplay() {
  const { data: form, loading, error } = formsClient.useForm({
    path: { slug: "contact" },
  });

  if (loading) return <div>Loading form...</div>;
  if (error) return <div>Error: {error.message}</div>;
  if (!form) return <div>Form not found</div>;

  return (
    <div>
      <h1>{form.title}</h1>
      <p>{form.description}</p>
      {/* Render form using form.dataSchema and form.uiSchema */}
    </div>
  );
}

useSubmitForm

Submits a response to a form. The form must have status open or static to accept submissions.

Parameters:

NameTypeRequiredDescription
path.slugstringYesThe form's slug
body.dataRecord<string, unknown>YesThe form response data
body.securityTokenstringNoOptional bot protection token (e.g., Turnstile)

Returns: string (the submission ID)

Error Codes: NOT_FOUND, VALIDATION_ERROR, FORM_NOT_OPEN

Example:

function ContactForm() {
  const { mutate: submit, data: responseId, loading, error } = formsClient.useSubmitForm();
  const [formData, setFormData] = useState({});

  const handleSubmit = async () => {
    await submit({
      path: { slug: "contact" },
      body: {
        data: formData,
        // securityToken: turnstileToken, // Optional
      },
    });
  };

  if (responseId) {
    return <p>Thank you! Submission ID: {responseId}</p>;
  }

  return (
    <form onSubmit={(e) => { e.preventDefault(); handleSubmit(); }}>
      {/* Form fields */}
      <button type="submit" disabled={loading}>
        {loading ? "Submitting..." : "Submit"}
      </button>
      {error && <p>Error: {error.message}</p>}
    </form>
  );
}

Admin Hooks - Forms

These hooks require authentication. Use middleware to secure /admin/* routes.

RouteDescriptionHook
GET /admin/formsList all formsuseForms
GET /admin/forms/:idFetch a form by IDuseFormById
POST /admin/formsCreate a new formuseCreateForm
PUT /admin/forms/:idUpdate a formuseUpdateForm
DELETE /admin/forms/:idDelete a formuseDeleteForm

useForms

Lists all forms, including both dynamic and static forms.

Parameters: None

Returns: Form[]

Example:

function FormsList() {
  const { data: forms, loading, error } = formsClient.useForms();

  if (loading) return <div>Loading...</div>;
  if (error) return <div>Error: {error.message}</div>;

  return (
    <ul>
      {forms?.map((form) => (
        <li key={form.id}>
          <span>{form.title}</span>
          <span>Status: {form.status}</span>
        </li>
      ))}
    </ul>
  );
}

useFormById

Fetches a single form by its ID. Useful when you have the form ID but not the slug.

Parameters:

NameTypeRequiredDescription
path.idstringYesThe form's unique ID

Returns: Form

Error Codes: NOT_FOUND

Example:

function FormEditor({ formId }: { formId: string }) {
  const { data: form, loading, error } = formsClient.useFormById({
    path: { id: formId },
  });

  if (loading) return <div>Loading...</div>;
  if (error) return <div>Error: {error.message}</div>;
  if (!form) return <div>Form not found</div>;

  return (
    <div>
      <h1>Editing: {form.title}</h1>
      <p>Slug: {form.slug}</p>
      <p>Version: {form.version}</p>
      {/* Form editor UI */}
    </div>
  );
}

useCreateForm

Creates a new dynamic form.

Parameters:

NameTypeRequiredDescription
body.titlestringYesForm title
body.slugstringYesURL-friendly identifier (unique)
body.status"draft" | "open" | "closed"YesForm status
body.dataSchemaRecord<string, unknown>YesJSON Schema for validation
body.uiSchemaRecord<string, unknown>NoJSONForms UI Schema
body.descriptionstringNoForm description

Returns: string (the new form's ID)

Example:

function CreateFormButton() {
  const { mutate: createForm, loading, error } = formsClient.useCreateForm();

  const handleCreate = async () => {
    const formId = await createForm({
      body: {
        title: "Feedback Survey",
        slug: "feedback-survey",
        status: "open",
        description: "Help us improve our service",
        dataSchema: {
          type: "object",
          properties: {
            rating: { type: "integer", minimum: 1, maximum: 5 },
            comments: { type: "string" },
          },
          required: ["rating"],
        },
        uiSchema: {
          type: "VerticalLayout",
          elements: [
            { type: "Control", scope: "#/properties/rating" },
            { type: "Control", scope: "#/properties/comments", options: { multi: true } },
          ],
        },
      },
    });
  };

  return (
    <div>
      <button onClick={handleCreate} disabled={loading}>
        Create Form
      </button>
      {error && <p>Error: {error.message}</p>}
    </div>
  );
}

useUpdateForm

Updates an existing form. All fields are optional - only include fields you want to change.

Static forms cannot be updated. Attempting to update a static form returns a STATIC_FORM_READ_ONLY error.

Changing the dataSchema increments the form's version number automatically.

Parameters:

NameTypeRequiredDescription
path.idstringYesThe form's ID
body.titlestringNoUpdated title
body.slugstringNoUpdated slug
body.status"draft" | "open" | "closed"NoUpdated status
body.dataSchemaRecord<string, unknown>NoUpdated JSON Schema
body.uiSchemaRecord<string, unknown>NoUpdated UI Schema
body.descriptionstringNoUpdated description

Returns: boolean

Error Codes: NOT_FOUND, STATIC_FORM_READ_ONLY

Example:

function CloseFormButton({ formId }: { formId: string }) {
  const { mutate: updateForm, loading, error } = formsClient.useUpdateForm();

  const handleClose = async () => {
    const success = await updateForm({
      path: { id: formId },
      body: { status: "closed" },
    });
  };

  return (
    <div>
      <button onClick={handleClose} disabled={loading}>
        Close Form
      </button>
      {error && <p>Error: {error.message}</p>}
    </div>
  );
}

useDeleteForm

Permanently deletes a form. This does not delete associated submissions.

Static forms cannot be deleted. Attempting to delete a static form returns a STATIC_FORM_READ_ONLY error.

Parameters:

NameTypeRequiredDescription
path.idstringYesThe form's ID

Returns: boolean

Error Codes: NOT_FOUND, STATIC_FORM_READ_ONLY

Example:

function DeleteFormButton({ formId }: { formId: string }) {
  const { mutate: deleteForm, loading, error } = formsClient.useDeleteForm();

  const handleDelete = async () => {
    if (!confirm("Are you sure you want to delete this form?")) return;

    const success = await deleteForm({
      path: { id: formId },
    });
  };

  return (
    <div>
      <button onClick={handleDelete} disabled={loading}>
        Delete Form
      </button>
      {error && <p>Error: {error.message}</p>}
    </div>
  );
}

Admin Hooks - Submissions

Hooks for managing form submissions.

RouteDescriptionHook
GET /admin/forms/:id/submissionsList submissions for a formuseSubmissions
GET /admin/submissions/:idGet a single submissionuseSubmission
DELETE /admin/submissions/:idDelete a submissionuseDeleteSubmission

useSubmissions

Lists all submissions for a specific form.

Parameters:

NameTypeRequiredDescription
path.idstringYesThe form's ID
query.sortOrder"asc" | "desc"NoSort by submission date (default: desc)

Returns: FormResponse[]

Prop

Type

Example:

function SubmissionsList({ formId }: { formId: string }) {
  const { data: submissions, loading, error } = formsClient.useSubmissions({
    path: { id: formId },
    query: { sortOrder: "desc" }, // Newest first
  });

  if (loading) return <div>Loading submissions...</div>;
  if (error) return <div>Error: {error.message}</div>;

  return (
    <table>
      <thead>
        <tr>
          <th>ID</th>
          <th>Submitted At</th>
          <th>Data</th>
        </tr>
      </thead>
      <tbody>
        {submissions?.map((submission) => (
          <tr key={submission.id}>
            <td>{submission.id}</td>
            <td>{new Date(submission.submittedAt).toLocaleString()}</td>
            <td>{JSON.stringify(submission.data)}</td>
          </tr>
        ))}
      </tbody>
    </table>
  );
}

useSubmission

Fetches a single submission by ID.

Parameters:

NameTypeRequiredDescription
path.idstringYesThe submission's ID

Returns: FormResponse

Error Codes: NOT_FOUND

Example:

function SubmissionDetail({ submissionId }: { submissionId: string }) {
  const { data: submission, loading, error } = formsClient.useSubmission({
    path: { id: submissionId },
  });

  if (loading) return <div>Loading...</div>;
  if (error) return <div>Error: {error.message}</div>;
  if (!submission) return <div>Submission not found</div>;

  return (
    <div>
      <h2>Submission Details</h2>
      <p>ID: {submission.id}</p>
      <p>Form ID: {submission.formId}</p>
      <p>Form Version: {submission.formVersion}</p>
      <p>Submitted: {new Date(submission.submittedAt).toLocaleString()}</p>
      <h3>Response Data:</h3>
      <pre>{JSON.stringify(submission.data, null, 2)}</pre>
    </div>
  );
}

useDeleteSubmission

Permanently deletes a submission.

Parameters:

NameTypeRequiredDescription
path.idstringYesThe submission's ID

Returns: boolean

Example:

function DeleteSubmissionButton({ submissionId }: { submissionId: string }) {
  const { mutate: deleteSubmission, loading, error } = formsClient.useDeleteSubmission();

  const handleDelete = async () => {
    if (!confirm("Are you sure you want to delete this submission?")) return;

    const success = await deleteSubmission({
      path: { id: submissionId },
    });
  };

  return (
    <div>
      <button onClick={handleDelete} disabled={loading}>
        Delete
      </button>
      {error && <p>Error: {error.message}</p>}
    </div>
  );
}