Forms to Convex Direct Mutation Plan
For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: Remove useFormSession hook and localStorage from forms. Forms submit directly to Convex via a single mutation call with all form data.
Architecture: Create submitFormData mutation that takes formType + all form fields as JSON and inserts directly to formSessions table. Forms call this one mutation on submit. No session ID, no localStorage, no debounced updates.
Tech Stack: Convex mutation, React Hook Form, Zod
Forms Affected
| Form | File | Status |
|---|---|---|
| WorkshopProposalForm | components/ui/workshop-proposal-form.tsx | Needs update |
| VenueRentalForm | components/ui/venue-rental-form.tsx | Needs update |
| PrivateEventsForm | components/ui/private-events-form.tsx | Needs update |
| ArtistProposalForm | components/ui/artist-proposal-form.tsx | Needs update |
| HostAnEventForm | components/ui/host-an-event-form.tsx | Needs update |
| ContactForm | components/ui/contact-form.tsx | Already uses server action — no change |
Files to modify:
apps/backend/convex/functions/form_sessions.ts— AddsubmitFormDatamutationapps/frontend/components/ui/workshop-proposal-form.tsxapps/frontend/components/ui/venue-rental-form.tsxapps/frontend/components/ui/private-events-form.tsxapps/frontend/components/ui/artist-proposal-form.tsxapps/frontend/components/ui/host-an-event-form.tsx
Files to delete:
apps/frontend/lib/hooks/use-form-session.ts
Task 1: Add submitFormData Mutation
File:
-
Modify:
apps/backend/convex/functions/form_sessions.ts -
Step 1: Add submitFormData mutation
Add this mutation to form_sessions.ts:
// Submit form data directly (no localStorage, no session ID)
export const submitFormData = mutation({
args: {
formType: v.union(
v.literal("CONTACT"),
v.literal("VENUE_RENTAL"),
v.literal("PRIVATE_EVENTS"),
v.literal("WORKSHOPS"),
v.literal("ARTIST_PROPOSAL"),
v.literal("HOST_AN_EVENT"),
),
data: v.string(), // JSON string of all form fields
},
handler: async (ctx, { formType, data }) => {
const now = Date.now();
// Generate UUID server-side — no client localStorage needed
const sessionId = `${Date.now()}-${Math.random().toString(36).substring(2, 11)}`;
await ctx.db.insert("formSessions", {
sessionId,
formType,
data,
submitted: true,
expiresAt: now + FORM_SESSION_TTL,
createdAt: now,
updatedAt: now,
});
return { success: true };
},
});- Step 2: Commit
git add apps/backend/convex/functions/form_sessions.ts
git commit -m "feat(forms): add submitFormData mutation for direct Convex submission"Task 2: Update WorkshopProposalForm
File:
-
Modify:
apps/frontend/components/ui/workshop-proposal-form.tsx -
Step 1: Remove useFormSession, add Convex mutation import
Replace:
import { useFormSession } from "@/lib/hooks/use-form-session";with:
import { useMutation } from "convex/react";
import { api } from "~/convex/_generated/api";- Step 2: Remove updateField and submit from useFormSession
Replace:
const { updateField, submit } = useFormSession("WORKSHOPS");with nothing (useMutation goes in component).
- Step 3: Add useMutation for submitFormData inside component
In the component function, add:
const submitForm = useMutation(api.formSessions.submitFormData);- Step 4: Remove the useEffect that calls updateField
Remove the entire:
useEffect(() => {
const subscription = form.watch((values) => {
Object.entries(values).forEach(([key, value]) => {
if (value !== undefined) {
updateField(key, value);
}
});
});
return () => subscription.unsubscribe();
}, [form, updateField]);- Step 5: Update onSubmit to call mutation directly
Replace:
const onSubmit = () => {
submit();
setIsSubmitted(true);
};with:
const onSubmit = async (data: WorkshopProposalFormData) => {
await submitForm({
formType: "WORKSHOPS",
data: JSON.stringify(data),
});
setIsSubmitted(true);
};And change form.handleSubmit(onSubmit) to pass the data:
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">- Step 6: Commit
git add apps/frontend/components/ui/workshop-proposal-form.tsx
git commit -m "refactor(forms): migrate workshop-proposal to direct Convex mutation"Task 3: Update VenueRentalForm
File:
- Modify:
apps/frontend/components/ui/venue-rental-form.tsx
Same pattern as Task 2:
- Remove
useFormSessionimport - Add
useMutationandapiimports - Add
const submitForm = useMutation(api.formSessions.submitFormData);in component - Remove
useEffectwithform.watch - Remove
{ updateField, submit }fromuseFormSession - Update
onSubmitto callsubmitFormwith{ formType: "VENUE_RENTAL", data: JSON.stringify(data) }
- Commit
git add apps/frontend/components/ui/venue-rental-form.tsx
git commit -m "refactor(forms): migrate venue-rental to direct Convex mutation"Task 4: Update PrivateEventsForm
File:
- Modify:
apps/frontend/components/ui/private-events-form.tsx
Same pattern:
- Remove
useFormSessionimport - Add
useMutationandapiimports - Add
const submitForm = useMutation(api.formSessions.submitFormData);in component - Remove
useEffectwithform.watch - Remove
{ updateField, submit }fromuseFormSession - Update
onSubmitto callsubmitFormwith{ formType: "PRIVATE_EVENTS", data: JSON.stringify(data) }
- Commit
git add apps/frontend/components/ui/private-events-form.tsx
git commit -m "refactor(forms): migrate private-events to direct Convex mutation"Task 5: Update ArtistProposalForm
File:
- Modify:
apps/frontend/components/ui/artist-proposal-form.tsx
Same pattern:
- Remove
useFormSessionimport - Add
useMutationandapiimports - Add
const submitForm = useMutation(api.formSessions.submitFormData);in component - Remove
useEffectwithform.watch - Remove
{ updateField, submit }fromuseFormSession - Update
onSubmitto callsubmitFormwith{ formType: "ARTIST_PROPOSAL", data: JSON.stringify(data) }
- Commit
git add apps/frontend/components/ui/artist-proposal-form.tsx
git commit -m "refactor(forms): migrate artist-proposal to direct Convex mutation"Task 6: Update HostAnEventForm
File:
- Modify:
apps/frontend/components/ui/host-an-event-form.tsx
Same pattern:
- Remove
useFormSessionimport - Add
useMutationandapiimports - Add
const submitForm = useMutation(api.formSessions.submitFormData);in component - Remove
useEffectwithform.watch - Remove
{ updateField, submit }fromuseFormSession - Update
onSubmitto callsubmitFormwith{ formType: "HOST_AN_EVENT", data: JSON.stringify(data) }
- Commit
git add apps/frontend/components/ui/host-an-event-form.tsx
git commit -m "refactor(forms): migrate host-an-event to direct Convex mutation"Task 7: Delete useFormSession Hook
File:
-
Delete:
apps/frontend/lib/hooks/use-form-session.ts -
Delete the file
git rm apps/frontend/lib/hooks/use-form-session.ts
git commit -m "chore: remove useFormSession hook (no longer needed)"Task 8: Verify No Remaining useFormSession Usage
- Step 1: Verify no usage remains
grep -r "useFormSession" apps/frontend --include="*.tsx" --include="*.ts"Expected: No matches (except raw/ directory)
- Commit
git commit -m "chore: verify no useFormSession usage remains"Self-Review Checklist
- Spec coverage: All 5 forms migrated, hook deleted
- No placeholders: All code is complete
- Type consistency:
formTypestring values match schema exactly - Contact form: Already uses server action — no change needed