Skip to content

Stale report value adds and removes characters during typing #1095

@transparent-citizen

Description

@transparent-citizen

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>
  );
};

StackBlitz

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    v2 plannedIssues planned for the future v2 export.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions