-
Notifications
You must be signed in to change notification settings - Fork 143
Description
Describe the bug and the expected behavior
🐛 Bug summary
Feature tests in #1079 have surfaced a bug where the application of targetValue (see report) can lead to faulty form state.
The behavior presents when using onChange={submitForm} and having expensive renders following that. Within this timeframe, targetValue can become stale: The user has already typed ahead, which causes the application of targetValue to be destructive. In brief, input text gets overwritten as the user types.
Expected: As the user types, if a stale targetValue arrives from the server, the form update is discarded. User input does not compete with intents or reports.
Actual: As the user types, if a stale targetValue arrives from the server, conform updates the form with stale values. Some input characters are removed or added as the user is adding or deleting characters.
Quoting @edmundhung:
This is gonna be really tricky to fix. Conform has no reliable way to know when a submission actually happens. We can detect when a submit event isn’t prevented, but that doesn’t cover submissions triggered with useSubmit(), which skips the event and reads form data directly from the DOM.
That’s why I’ve been wondering if it’s reasonable to compare the current form value with the one the submission result was based on, to decide whether it’s still valid to use instead.
💻 Reproduction demo
To demonstrate this, the following setup features an <input> with onInput={submitForm} that renders 4,000 random numbers after every submission. This is to simulate a search page with a long result list.
Code
import { parseSubmission, report } from '@conform-to/dom/future';
import { useForm } from '@conform-to/react/future';
import { coerceFormValue } from '@conform-to/zod/v4/future';
import type { FormEventHandler } from 'react';
import { Form, useSubmit } from 'react-router';
import z from 'zod';
import type { Route } from './+types/home';
const schema = coerceFormValue(
z.object({
input: z.string().optional(),
})
);
export const loader = async ({ request }: Route.ActionArgs) => {
const searchParams = new URL(request.url).searchParams;
const submission = parseSubmission(searchParams);
const result = schema.safeParse(submission.payload);
if (!result.success) {
throw result.error;
}
return {
lastResult: result.data.input
? report(submission, {
targetValue: {
input: result.data.input,
},
})
: null,
};
};
export default ({ loaderData }: Route.ComponentProps) => {
const { form, fields } = useForm({
...loaderData,
schema,
});
const submit = useSubmit();
const submitForm: FormEventHandler<HTMLInputElement> = (event) => {
if (!event.currentTarget.form) {
return;
}
submit(event.currentTarget.form, {
replace: true,
});
};
return (
<Form {...form.props}>
<input name={fields.input.name} onInput={submitForm} />
<button type="submit">Submit</button>
{new Array(4000).fill(0).map((_, i) => (
<div key={i}>{Math.random()}</div>
))}
</Form>
);
};Conform version
v1.13.3
Steps to Reproduce the Bug or Issue
If you reproduce it locally, the following manual intervention is not necessary. But on StackBlitz, hit submit once before trusting the application to be properly initialized.
Once initialized, enter any text to observe the text mangling.
What browsers are you seeing the problem on?
Others
Screenshots or Videos
No response
Additional context
No response