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

fix(react-form): removing array items (#1171) #1319

Open
wants to merge 2 commits into
base: main
Choose a base branch
from

Conversation

niba
Copy link

@niba niba commented Mar 21, 2025

It is impossible to remove an item from an array while properly using React keys.

Here is an example taken from the issue that demonstrates the problem. Try to remove the middle item.

This issue occurs because we use the ⁠index inside the field name. When you remove any item other than the last one, the array is re-iterated, and in the next render, the field names change.
In the current code, you update the name as the last action and use the old name in earlier actions.

Let's assume you have an array: ⁠[{ id: 1, name: "one"}, {id: 2, name: "two"}, {id: 3, name: "three"}], and you remove the middle item. In the next render loop, you want to render ⁠[{ id: 1, name: "one"}, {id: 3, name: "three"}]. However, for the item with ⁠id = 3, the store tries to access the value using the name ⁠"items[2].value" instead of ⁠"items[1].value", which causes unexpected effects (see the example).

This bug doesn't occur when you use the index as a key (which is not correct). In this scenario, React behaves a little differently and doesn't cause a re-render.

I'm not familiar with the entire codebase, so I don't know if this change could have any side effects.

@crutchcorn
Copy link
Member

Can we add tests to verify that we don't regress problems?

@niba
Copy link
Author

niba commented Mar 26, 2025

Sure. Sorry, I got so excited that I managed to fix it after a long debugging session that I forgot about the tests. 😸

@niba niba force-pushed the fix/array_mutations branch 3 times, most recently from 59f92ea to 537c513 Compare March 27, 2025 13:08
@niba
Copy link
Author

niba commented Mar 27, 2025

@crutchcorn I added a test and found another problem that I resolved.

After my first change, the array fields started rendering correctly, but the displayed values were wrong.

The reason was that defaultValue was not updated after removing any item in the array. In the next render, FieldApi calls setValue with a defaultValue that is not valid because it belongs to the removed field.

The flow:

defaultValues: ["a", "b", "c"]

render: 
"a",
"b",
"c",

remove "b"

render:
"a"
"b" WRONG_VALUE ->  this.state.value is undefined, so it takes value from ⁠defaultValues where ⁠defaultValue[1] === "b". This one wins because the updater is more important than store calculation

To fix this I added new code that modifies how ⁠setValue is called when a name change is detected.

@niba niba force-pushed the fix/array_mutations branch from 537c513 to 9921de9 Compare March 27, 2025 13:24
Copy link

nx-cloud bot commented Mar 28, 2025

View your CI Pipeline Execution ↗ for commit 39e82b3.

Command Status Duration Result
nx affected --targets=test:sherif,test:knip,tes... ✅ Succeeded 2m 11s View ↗

☁️ Nx Cloud last updated this comment at 2025-03-28 10:18:45 UTC

Copy link

pkg-pr-new bot commented Mar 28, 2025

Copy link

codecov bot commented Mar 28, 2025

Codecov Report

Attention: Patch coverage is 75.00000% with 2 lines in your changes missing coverage. Please review.

Project coverage is 88.88%. Comparing base (516f587) to head (39e82b3).
Report is 7 commits behind head on main.

Files with missing lines Patch % Lines
packages/form-core/src/FieldApi.ts 75.00% 1 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1319      +/-   ##
==========================================
+ Coverage   88.86%   88.88%   +0.01%     
==========================================
  Files          28       28              
  Lines        1275     1277       +2     
  Branches      332      335       +3     
==========================================
+ Hits         1133     1135       +2     
+ Misses        127      126       -1     
- Partials       15       16       +1     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@Balastrong
Copy link
Member

Balastrong commented Mar 28, 2025

Can you please also add some tests to validate the new default logic?

At first the fix seems to be working, well done :)

@niba
Copy link
Author

niba commented Mar 28, 2025

The line not covered by tests is an old logic line. I needed to add a new 'if' branch, so I refactored the code slightly to make it easier to understand.

The default value logic remains the same and should be covered by the tests introduced here: GitHub Pull Request #445.

I only added an exception to not use the default value when the name has changed. This is covered by the array tests, where I check if the values are correct after removing an element.

If you want, I can add some tests, but they would duplicate existing ones (setting the default value on mount).

Thanks!

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

Successfully merging this pull request may close these issues.

3 participants