conform
conform copied to clipboard
Unexpected navigation can occur with Remix useFetcher and resetForm: true
Describe the bug and the expected behavior
I've noticed some funny interplay between using Remix's useFetcher and conform's resetForm option, specifically when the useFetcher hits an action from another route. I've created a sandbox that shows this issue here:
https://codesandbox.io/p/devbox/interesting-hamilton-xt5pwn
Specifically, if the user fills out a name and hits enter to submit the form, they're brought to the JSON response that the action returns. Interestingly, if the user clicks the submit button, the form works as expected.
Conform version
1.1.5
Steps to Reproduce the Bug or Issue
https://codesandbox.io/p/devbox/interesting-hamilton-xt5pwn
Alternatively, create a basic Remix app with zod, @conform-to/react, and @conform-to/zod installed.
Update routes/index.tsx
import { getFormProps, getInputProps, useForm } from "@conform-to/react";
import { getZodConstraint, parseWithZod } from "@conform-to/zod";
import { json, type MetaFunction } from "@remix-run/node";
import { useActionData, useLoaderData } from "@remix-run/react";
import { useFetcher } from "react-router-dom";
import { z } from "zod";
import { action as createUserAction } from "./create-user";
export const users = [{ name: "Austin" }];
const schema = z.object({
name: z.string().min(2),
});
export const meta: MetaFunction = () => {
return [
{ title: "New Remix App" },
{ name: "description", content: "Welcome to Remix!" },
];
};
export async function loader() {
return json(users);
}
export default function Index() {
const users = useLoaderData<typeof loader>();
const fetcher = useFetcher<typeof createUserAction>();
const lastResult = useActionData<typeof createUserAction>();
const [form, fields] = useForm({
lastResult: fetcher.state === "idle" ? fetcher?.data : null,
constraint: getZodConstraint(schema),
shouldValidate: "onBlur",
shouldRevalidate: "onInput",
onValidate({ formData }) {
return parseWithZod(formData, { schema });
},
defaultValue: {
name: "",
},
});
return (
<div style={{ fontFamily: "system-ui, sans-serif", lineHeight: "1.8" }}>
<fetcher.Form method="post" action="/create-user" {...getFormProps(form)}>
<p>
This example uses a Remix fetcher to submit data to another route. If
you click "Submit", you'll see things work as expected. The route is
accessed, and returns a submission.result that clears the input.
</p>
<p>
But if you fill in a value in the input and hit Enter, you're
unexpectedly navigated to the remote route, which returns JSON.
</p>
<label>User Name</label>
<br />
<input {...getInputProps(fields.name, { type: "text" })} />
{fields.name.errors && <div>{fields.name.errors[0]}</div>}
<br /> <input type="submit" value="Submit" />
</fetcher.Form>
{users.map((user, index) => (
<div key={index}>{user.name}</div>
))}
</div>
);
}
Create routes/create-user.tsx
import { parseWithZod } from "@conform-to/zod";
import { ActionFunctionArgs } from "@remix-run/node";
import { users } from "./_index";
import { z } from "zod";
const schema = z.object({
name: z.string().min(4),
});
export async function action({ request }: ActionFunctionArgs) {
const formData = await request.formData();
const submission = parseWithZod(formData, { schema });
if (submission.status !== "success") {
return submission.reply();
}
users.push({
name: submission.value.name,
});
return submission.reply({
resetForm: true,
});
}
What browsers are you seeing the problem on?
Chrome
Screenshots or Videos
No response
Additional context
I suppose it's possible this is a Remix issue, but seems to specifically happen when the submission.response({ resetForm: true })
option is configured.