final-form / react-final-form

🏁 High performance subscription-based form state management for React
https://final-form.org/react
MIT License
7.39k stars 481 forks source link

Bug: Initial value does not match value on useFormState #887

Open wdfinch opened 3 years ago

wdfinch commented 3 years ago

Are you submitting a bug report or a feature request?

Bug report

What is the current behavior?

If

  1. A <Field/> is mounted after the parent <Form/> has already been mounted in a separate child component
  2. The <Field/> is provided an initialValue
  3. The form values are extracted via useFormState in the separate child component

Then

The form value of the <Field/> is undefined rather than being the initialValue on the first mount. After the first mount, the correct value is shown.

What is the expected behavior?

The form value of the <Field/> should be the initial value and not undefined.

Sandbox Link

https://codesandbox.io/s/stoic-mountain-cdjf9?file=/index.js

What's your environment?

React Final Form version: 6.5.2 Final Form version: 4.20.1 OS: Ubuntu 20.04 LTS Browser: Chrome 87.0.4280.141 (Official Build) (64-bit) Node: v15.5.1

Other information

To reproduce, simply click the show button in the sandbox. Once clicked, look at the console, and observe the undefined is printed rather than the initialValue (in my case the string "Sam").

My current workaround is to use the changeValue mutation to initialized the <Field/> rather than initialValue, but this is not as clean.

sanderdewilde commented 3 years ago

When calling the useFormState hook, your input field has not been created yet, and so it initialises with value undefined. When your input field is created, it sends an event to its listeners, but the event is ignored by the listener. See #890 as to why this happens.

If you would change the order of using useFormState and Field, the behaviour would be as expected:

const Listener = () => {
  const formState = useFormState();
  const formValues = formState.values;
  const firstName = formValues.firstName;

  // first name now is "Sam"
  console.log(firstName);

  return null;
}

const Child = () => {
  return (
    <div>
      <Field
        name="firstName"
        component="input"
        initialValue={"Sam"}
        type="text"
        placeholder="First Name"
      />
      <Listener />
    </div>
  );
};

I've fixed this for my own project by creating a fork of the library with the implementation change mentioned in #890.