form icon indicating copy to clipboard operation
form copied to clipboard

Removing array field item doesn't work inside of transition group, when there's a default value

Open ej-shafran opened this issue 5 months ago • 11 comments

Describe the bug

This bug was extremely confusing but I finally managed to reproduce it minimally.

When rendering a TransitionGroup inside of the children callback of an array form.Field, and rendering sub-fields inside of that TransitionGroup, removing any of the items re-creates the last item of the array until the array is back to the size it had in defaultValues.

Your minimal, reproducible example

https://stackblitz.com/edit/tanstack-form-c9w5c8ae?file=src%2Findex.tsx&preset=node

Steps to reproduce

  1. Go to the provided link
  2. Click on delete next to any of the inputs
  3. See that the last input is re-added with the value "c"

Expected behavior

I expected the last field to be properly removed from the rendered list

How often does this bug happen?

Every time

Screenshots or Videos

No response

Platform

  • OS: macOS
  • Browser: Chrome
  • Browser version: Version 138.0.7204.169 (Official Build) (arm64)

TanStack Form adapter

react-form

TanStack Form version

v1.15.0

TypeScript version

No response

Additional context

react-form's useField hook calls field.update inside of useIsomorphicLayoutEffect. Usually this isn't an issue, but when using the field inside of a TransitionGroup from react-transition-group, the effect fires when the field's state has been removed, before the element itself is properly unmounted, which causes the removed item to be re-added to the state.

This bug goes away if I don't use a nested form.Field - and manually do the logic for the nested handleChange. Is there any way to get the benefit of nested form.Fields without this issue...?

(BTW, don't know if this should be a separate issue, but the link for the "official CodeSandbox examples" in the issue template is wrong...)

ej-shafran avatar Jul 28 '25 15:07 ej-shafran

https://react.dev/learn/rendering-lists#why-does-react-need-keys :)

kusiewicz avatar Jul 28 '25 16:07 kusiewicz

Hey @kusiewicz - if you take a look at the example reproduction I created, you can see that I have actually used keys for the rendered items in the list, in a way that I believe shouldn't cause any issues and which is very similar to the official examples.

Here is an additional example with static IDs for the items instead of indexes, in case you think that was the problem - the issue persists. https://stackblitz.com/edit/tanstack-form-ddsbgbux?file=src%2Findex.tsx&preset=node

ej-shafran avatar Jul 28 '25 16:07 ej-shafran

Probably related to #1577 ? But considering that this issue is about react-form implies it's likely a form-core issue.

Thanks for the reproducible example!

LeCarbonator avatar Jul 29 '25 05:07 LeCarbonator

Managed to get the reproduction to be even simpler:

https://stackblitz.com/edit/tanstack-form-dysng9a7?file=src%2Findex.tsx&preset=node

I've just used a function component that does basically the same logic as what react-transition-group does

ej-shafran avatar Jul 29 '25 09:07 ej-shafran

The array removal should now work with this. However, the TransitionGroup not working properly can be a side effect from how useSyncExternalStore works with transition groups, which can be broken in React v19. It's best if you install the PR version (command found in PR) to confirm whether or not it works.

LeCarbonator avatar Sep 29 '25 14:09 LeCarbonator

Just want to add that we are also experiencing this issue. We don't use react-transition-group, but we use react-framer-motion. But we do experience that, when we have the default value being an array of multiple objects, we have some strange behaviour when we delete the last one. What is even more interesting, is that it doesn't keep all properties of the object just deleted, but just some of them.

Our workaround for now is to always initiate the form with an empty array, and then set the initial values in a useEffect after the form is initiated.

Not idea, but it works for now.

aburto22 avatar Oct 07 '25 17:10 aburto22

@aburto22 Could you install the #1729 version (script in PR) and let me know if the problem is fixed by it?

LeCarbonator avatar Oct 08 '25 16:10 LeCarbonator

@aburto22 Could you install the #1729 version (script in PR) and let me know if the problem is fixed by it?

Hey! Just tried the version in #1729 and it seems to fix our issue. Good job!

aburto22 avatar Oct 09 '25 12:10 aburto22

@LeCarbonator installing the PR in this reproduction doesn't seem to fix the issue...

https://stackblitz.com/edit/tanstack-form-dysng9a7?file=package.json

ej-shafran avatar Oct 15 '25 07:10 ej-shafran

@ej-shafran Hmm ... it might be the transition group - related issue then.

From what I know, useSyncExternalStore does not work together with React transition group because they cannot be marked as non-urgent. TanStack Store uses that hook, and TanStack Form uses TanStack Store.

The Transition Group is not only related to defaultValues. Notice how you can't use pushValue either.

LeCarbonator avatar Oct 15 '25 07:10 LeCarbonator

@LeCarbonator

The reproduction doesn't actually use react-transition-group but instead a minimal component that creates the issue. Maybe the PR you've linked to is relevant to a different issue...?

ej-shafran avatar Oct 19 '25 08:10 ej-shafran