Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bug: useField can not get newest value #984

Open
xxleyi opened this issue Apr 14, 2022 · 7 comments
Open

Bug: useField can not get newest value #984

xxleyi opened this issue Apr 14, 2022 · 7 comments

Comments

@xxleyi
Copy link

xxleyi commented Apr 14, 2022

When I change form value in useEffect without event trigger or network delay, useField can not get the newest value.

Demo link is here: https://codesandbox.io/s/react-final-form-usefield-can-not-get-newest-value-gc3do4?file=/index.js

@makap0120
Copy link

Experiencing same issue.
@xxleyi Did you manage to find any workaround?

Bump.

@xxleyi
Copy link
Author

xxleyi commented Apr 22, 2022

@makap0120 Just like in demo, we are using useFormState to workaround this, but it is annoying, because we can not just use useFormState everywhere, only after we were bitten by some bugs.

@makap0120
Copy link

@makap0120 Just like in demo, we are using useFormState to workaround this, but it is annoying, because we can not just use useFormState everywhere, only after we were bitten by some bugs.

Thanks for quick reply :)

Oh i see, this doesn't seem as optimal solution in my case - to display field value i'm using renderProps.input.value received from wrapper <Field subscribe={{value: true}} /> component and reusing corresponding field name prop. This being done for performance reasons, so would like to avoid hooking into form state for same reason.

I wonder why field subscribers miss this exact field change value change performed in useEffect during initial render..

@wilysword
Copy link

This same bug applies to useFormState; it specifically has to do with the firstRender ref (https://github.com/final-form/react-final-form/blob/main/src/useField.js#L116). Here's what I think happens:

  1. On the initial render, the state is set by registering the field, then immediately unregistering it, inside a useState(() => initializerCallback).
  2. Rendering continues down the tree.
  3. Once the effect phase is reached, the "permanent" call to registerField is made in a useEffect hook; however, a ref is used so that on the first call to the state callback, the state is not set.

It does this to avoid a double render, but it assumes that the state cannot change between the initial render and the effect phase, which as your use case illustrates, is incorrect. The effect phase runs depth-first, so any number of effect hooks can change the form state between the initializer and the permanent subscription. In your example specifically, the effect hook in the LastName component runs before the one in FavoriteColor, meaning the subscription in FavoriteColor just misses the change.

(I was looking into it because I was using a FormSpy subscribed to hasValidationErrors, and it was returning false despite a number of field-level validators returning initial errors on required fields.)

@xxleyi
Copy link
Author

xxleyi commented Oct 8, 2022

@makap0120 Recently I am trying create a custom hook to reactive some (not all) values of final form by use of useSyncExternalStore, here is the demo: xxleyi/learning_list#336

@iamdey
Copy link

iamdey commented Nov 7, 2022

Hi, I think I have experienced the same issue but in a different case: with conditional fields.
Even with Field component, it won't retrieve the programmatically changed value on first render:

https://codesandbox.io/s/react-final-form-conditional-fields-forked-pllcsp?file=/src/index.js

I don't think I can use the useFormState as workaround in that case :/

@montes5
Copy link

montes5 commented Feb 7, 2023

@xxleyi @makap0120 @wilysword @iamdey
May be helpful for those who's still looking for a workaround

import { FormApi } from 'final-form';
import { useForm } from 'react-final-form';
import { useState, useEffect, useCallback } from 'react';
import _get from 'lodash/get';

type GetFormValue = (form: FormApi, name: string) => any;

const getFormValue: GetFormValue = (form, name) => _get(form.getState().values, name) ?? '';

type UseFormValue = <V, >(name: string) => [V, (v: V) => void];

export const useFormValue: UseFormValue = (name) => {
  const form = useForm();

  const [, updateComponent] = useState(0);

  let value = getFormValue(form, name);
  const setValue = useCallback((v: any) => form.change(name, v), [name]);

  useEffect(() => {
    const unsubscribe = form.subscribe(
      ({ values }) => {
        const formValue = _get(values, name) ?? '';

        if (formValue !== value) {
          value = formValue;
          updateComponent((v) => v + 1);
        }
      },
      { values: true },
    );

    return () => {
      unsubscribe();
    };
  }, [name, value]);

  return [value, setValue];
};

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants