Skip to content

Commit 2761a35

Browse files
committed
chore(webapp): upgrade @conform-to to v1
Migrate the dashboard form layer from @conform-to 0.9 to the 1.x API. conform 1.x peer-depends on zod ^3.21 || ^4, so it runs on the current zod 3 and unblocks the zod 4 upgrade (conform 0.9 cannot bundle against zod 4, which removed ZodNativeEnum, ZodEffects, and ZodPipeline).
1 parent d565949 commit 2761a35

47 files changed

Lines changed: 779 additions & 774 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.server-changes/conform-v1.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
area: webapp
3+
type: improvement
4+
---
5+
6+
Upgrade the dashboard form layer from `@conform-to` 0.9 to 1.x. conform 1.x supports both zod 3 and zod 4, which unblocks the upcoming zod 4 upgrade.

apps/webapp/app/components/Feedback.tsx

Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import { conform, useForm } from "@conform-to/react";
2-
import { parse } from "@conform-to/zod";
1+
import { getFormProps, getSelectProps, getInputProps, getTextareaProps, useForm } from "@conform-to/react";
2+
import { parseWithZod } from "@conform-to/zod";
33
import { InformationCircleIcon, ArrowUpCircleIcon } from "@heroicons/react/20/solid";
44
import { EnvelopeIcon, ShieldCheckIcon } from "@heroicons/react/24/solid";
55
import { Form, useActionData, useLocation, useNavigation, useSearchParams } from "@remix-run/react";
@@ -34,11 +34,11 @@ export function Feedback({ button, defaultValue = "bug", onOpenChange }: Feedbac
3434
const navigation = useNavigation();
3535
const [type, setType] = useState<FeedbackType>(defaultValue);
3636

37-
const [form, { path, feedbackType, message }] = useForm({
37+
const [form, fields] = useForm({
3838
id: "accept-invite",
39-
lastSubmission: lastSubmission as any,
39+
lastResult: lastSubmission as any,
4040
onValidate({ formData }) {
41-
return parse(formData, { schema });
41+
return parseWithZod(formData, { schema });
4242
},
4343
shouldRevalidate: "onInput",
4444
});
@@ -47,8 +47,7 @@ export function Feedback({ button, defaultValue = "bug", onOpenChange }: Feedbac
4747
if (
4848
navigation.formAction === "/resources/feedback" &&
4949
navigation.state === "loading" &&
50-
form.error === undefined &&
51-
form.errors.length === 0
50+
(form.errors === undefined || form.errors.length === 0)
5251
) {
5352
setOpen(false);
5453
}
@@ -90,9 +89,9 @@ export function Feedback({ button, defaultValue = "bug", onOpenChange }: Feedbac
9089
type === "concurrency" ||
9190
type === "hipaa"
9291
) && <hr className="border-grid-dimmed" />}
93-
<Form method="post" action="/resources/feedback" {...form.props} className="w-full">
92+
<Form method="post" action="/resources/feedback" {...getFormProps(form)} className="w-full">
9493
<Fieldset className="max-w-full gap-y-3">
95-
<input value={location.pathname} {...conform.input(path, { type: "hidden" })} />
94+
<input value={location.pathname} {...getInputProps(fields.path, { type: "hidden" })} />
9695
<InputGroup className="max-w-full">
9796
{type === "feature" && (
9897
<InfoPanel
@@ -149,7 +148,7 @@ export function Feedback({ button, defaultValue = "bug", onOpenChange }: Feedbac
149148
</InfoPanel>
150149
)}
151150
<Select
152-
{...conform.select(feedbackType)}
151+
{...getSelectProps(fields.feedbackType)}
153152
variant="tertiary/medium"
154153
value={type}
155154
defaultValue={type}
@@ -164,14 +163,14 @@ export function Feedback({ button, defaultValue = "bug", onOpenChange }: Feedbac
164163
</SelectItem>
165164
))}
166165
</Select>
167-
<FormError id={feedbackType.errorId}>{feedbackType.error}</FormError>
166+
<FormError id={fields.feedbackType.errorId}>{fields.feedbackType.errors}</FormError>
168167
</InputGroup>
169168
<InputGroup className="max-w-full">
170169
<Label>Message</Label>
171-
<TextArea {...conform.textarea(message)} />
172-
<FormError id={message.errorId}>{message.error}</FormError>
170+
<TextArea {...getTextareaProps(fields.message)} />
171+
<FormError id={fields.message.errorId}>{fields.message.errors}</FormError>
173172
</InputGroup>
174-
<FormError>{form.error}</FormError>
173+
<FormError>{form.errors}</FormError>
175174
<FormButtons
176175
confirmButton={
177176
<Button type="submit" variant="primary/medium">

apps/webapp/app/components/errors/ConfigureErrorAlerts.tsx

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import { conform, list, requestIntent, useFieldList, useForm } from "@conform-to/react";
2-
import { parse } from "@conform-to/zod";
1+
import { getFormProps, getInputProps, useForm } from "@conform-to/react";
2+
import { parseWithZod } from "@conform-to/zod";
33
import {
44
EnvelopeIcon,
55
GlobeAltIcon,
@@ -104,20 +104,21 @@ export function ConfigureErrorAlerts({
104104
existingWebhooks.length > 0 ? [...existingWebhooks.map((w) => w.url), ""] : [""]
105105
);
106106

107-
const [form, { emails, webhooks, slackChannel, slackIntegrationId }] = useForm({
107+
const [form, fields] = useForm<z.infer<typeof ErrorAlertsFormSchema>>({
108108
id: "configure-error-alerts",
109109
onValidate({ formData }) {
110-
return parse(formData, { schema: ErrorAlertsFormSchema });
110+
return parseWithZod(formData, { schema: ErrorAlertsFormSchema });
111111
},
112112
shouldRevalidate: "onSubmit",
113113
defaultValue: {
114114
emails: emailFieldValues.current,
115115
webhooks: webhookFieldValues.current,
116116
},
117117
});
118+
const { emails, webhooks, slackChannel, slackIntegrationId } = fields;
118119

119-
const emailFields = useFieldList(form.ref, emails);
120-
const webhookFields = useFieldList(form.ref, webhooks);
120+
const emailFields = emails.getFieldList();
121+
const webhookFields = webhooks.getFieldList();
121122

122123
return (
123124
<div className="grid h-full grid-rows-[auto_1fr_auto] overflow-hidden">
@@ -138,7 +139,7 @@ export function ConfigureErrorAlerts({
138139
<fetcher.Form
139140
method="post"
140141
action={formAction}
141-
{...form.props}
142+
{...getFormProps(form)}
142143
className="contents"
143144
>
144145
<div className="flex-1 overflow-y-auto scrollbar-thin scrollbar-track-transparent scrollbar-thumb-charcoal-600">
@@ -160,7 +161,7 @@ export function ConfigureErrorAlerts({
160161
{emailFields.map((emailField, index) => (
161162
<Fragment key={emailField.key}>
162163
<Input
163-
{...conform.input(emailField, { type: "email" })}
164+
{...getInputProps(emailField, { type: "email" })}
164165
placeholder={index === 0 ? "Enter an email address" : "Add another email"}
165166
icon={EnvelopeIcon}
166167
onChange={(e) => {
@@ -169,11 +170,11 @@ export function ConfigureErrorAlerts({
169170
emailFields.length === emailFieldValues.current.length &&
170171
emailFieldValues.current.every((v) => v !== "")
171172
) {
172-
requestIntent(form.ref.current ?? undefined, list.append(emails.name));
173+
form.insert({ name: emails.name });
173174
}
174175
}}
175176
/>
176-
<FormError id={emailField.errorId}>{emailField.error}</FormError>
177+
<FormError id={emailField.errorId}>{emailField.errors}</FormError>
177178
</Fragment>
178179
))}
179180
</InputGroup>
@@ -320,7 +321,7 @@ export function ConfigureErrorAlerts({
320321
{webhookFields.map((webhookField, index) => (
321322
<Fragment key={webhookField.key}>
322323
<Input
323-
{...conform.input(webhookField, { type: "url" })}
324+
{...getInputProps(webhookField, { type: "url" })}
324325
placeholder={
325326
index === 0 ? "https://example.com/webhook" : "Add another webhook URL"
326327
}
@@ -331,18 +332,18 @@ export function ConfigureErrorAlerts({
331332
webhookFields.length === webhookFieldValues.current.length &&
332333
webhookFieldValues.current.every((v) => v !== "")
333334
) {
334-
requestIntent(form.ref.current ?? undefined, list.append(webhooks.name));
335+
form.insert({ name: webhooks.name });
335336
}
336337
}}
337338
/>
338-
<FormError id={webhookField.errorId}>{webhookField.error}</FormError>
339+
<FormError id={webhookField.errorId}>{webhookField.errors}</FormError>
339340
</Fragment>
340341
))}
341342
<Hint>We'll issue POST requests to these URLs with a JSON payload.</Hint>
342343
</InputGroup>
343344
</div>
344345

345-
<FormError>{form.error}</FormError>
346+
<FormError>{form.errors}</FormError>
346347
</Fieldset>
347348
</div>
348349

0 commit comments

Comments
 (0)