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

When you render Field conditionally first rendered field will override all next rendered fields #808

Open
vara855 opened this issue Jul 2, 2024 · 3 comments

Comments

@vara855
Copy link
Contributor

vara855 commented Jul 2, 2024

Describe the bug

Use case:

I want to render different Field components according to some condition of form state.values.

It used to work in previous versions (0.21=<).

Your minimal, reproducible example

https://stackblitz.com/edit/vitejs-vite-dvwuju?file=src%2FApp.jsx

Steps to reproduce

  1. go to https://stackblitz.com/edit/vitejs-vite-dvwuju?file=src%2FApp.jsx
  2. fill input
  3. click checkbox
  4. check values of form

Expected behavior

I expected that values firstField value will dissapear from state when you uncheck checkbox and secondField value will be stored in state.

How often does this bug happen?

Every time

Screenshots or Videos

No response

Platform

Arc browser

TanStack Form adapter

react-form

TanStack Form version

v0.25.1

TypeScript version

=v5.4.5

Additional context

No response

@vara855 vara855 changed the title When you render Field conditionally first rendered filed will override all next rendered fields When you render Field conditionally first rendered field will override all next rendered fields Jul 2, 2024
@vara855
Copy link
Contributor Author

vara855 commented Jul 2, 2024

upd: You may fix it if you render it under different Subscribe components. But it feels like a bug...

@BenJenkinson
Copy link

BenJenkinson commented Jul 15, 2024

You can also "fix" it by applying a key prop to each of the <Field> instances that would occupy the same position in the tree, (e.g. <Field key="first" name="firstField">).

Because of that, I think the problem is that the <Field> component and useField state not "noticing" when the name prop has changed.

Here, we see that the fieldApi instance is created once with the initial name value, and then kept in state.

const [fieldApi] = useState(() => {
const api = new FieldApi({
...opts,
form: opts.form,
name: opts.name,
})
const extendedApi: typeof api & ReactFieldApi<TParentData, TFormValidator> =
api as never
extendedApi.Field = Field as never
return extendedApi
})

The value of this.name is only set when a new instance is constructed:

/**
* Initializes a new `FieldApi` instance.
*/
constructor(
opts: FieldApiOptions<
TParentData,
TName,
TFieldValidator,
TFormValidator,
TData
>,
) {
this.form = opts.form as never
this.name = opts.name as never

And the field's getValue and setValue functions use the initial this.name value:

/**
* Gets the current field value.
* @deprecated Use `field.state.value` instead.
*/
getValue = (): TData => {
return this.form.getFieldValue(this.name) as TData
}
/**
* Sets the field value and run the `change` validator.
*/
setValue = (updater: Updater<TData>, options?: UpdateMetaOptions) => {
this.form.setFieldValue(this.name, updater as never, options)
this.validate('change')
}

In the update function, we can see that it uses the opt.name value to get the correct default value, but doesn't update the stored this.name to be the new value of opts.name; so getValue/setValue continue to use the (initial, now incorrect) value.

update = (
opts: FieldApiOptions<
TParentData,
TName,
TFieldValidator,
TFormValidator,
TData
>,
) => {
// Default Value
if (this.state.value === undefined) {
const formDefault = getBy(opts.form.options.defaultValues, opts.name)
if (opts.defaultValue !== undefined) {
this.setValue(opts.defaultValue as never, {
dontUpdateMeta: true,
})
} else if (formDefault !== undefined) {
this.setValue(formDefault as never, {
dontUpdateMeta: true,
})
}
}
// Default Meta
if (this._getMeta() === undefined) {
this.setMeta(this.state.meta)
}
this.options = opts as never
}

@vara855
Copy link
Contributor Author

vara855 commented Jul 20, 2024

@BenJenkinson Yep, everything works as you explained. Thank you for explanation. I haven't tried to dive into implementation. In my codebase I've fixed it with two Subscribe blocks.

For me it looks like unhandled name property of opts in update.

I think it's worth fixing that. I may do a PR on that. Either it's worth reflecting in the doc. @crutchcorn what do you think? Do you mind if I fix it?

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

2 participants