Skip to content

Commit dfcebdb

Browse files
committed
Revert "🐞 fix #13260: notify all matching field-array roots on nested setValue updates (#13420)"
This reverts commit c6c3d87.
1 parent ca01f65 commit dfcebdb

7 files changed

Lines changed: 52 additions & 177 deletions

File tree

src/__tests__/logic/getFieldArrayParentNames.test.ts

Lines changed: 0 additions & 61 deletions
This file was deleted.
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import isNameInFieldArray from '../../logic/isNameInFieldArray';
2+
3+
describe('isNameInFieldArray', () => {
4+
it('should find match array field', () => {
5+
expect(isNameInFieldArray(new Set(['test']), 'test.0')).toBeTruthy();
6+
expect(isNameInFieldArray(new Set(['te']), 'test.0')).toBeFalsy();
7+
expect(isNameInFieldArray(new Set(['te']), 'test.0')).toBeFalsy();
8+
expect(isNameInFieldArray(new Set(['test1']), 'test[0]')).toBeFalsy();
9+
expect(isNameInFieldArray(new Set(['test1']), 'test.0')).toBeFalsy();
10+
expect(
11+
isNameInFieldArray(new Set(['test']), 'test.0.data[0]'),
12+
).toBeTruthy();
13+
expect(isNameInFieldArray(new Set(['test']), 'test.0.data.0')).toBeTruthy();
14+
expect(isNameInFieldArray(new Set(['test']), 'test1.0.data.0')).toBeFalsy();
15+
expect(isNameInFieldArray(new Set(['test']), 'data.0.data.0')).toBeFalsy();
16+
});
17+
18+
it('should find match when field array is nested under a numeric path segment', () => {
19+
// Field array registered at `steps.0.items`, Controller name is `steps.0.items.2.name`
20+
// getNodeParentName returns `steps` (first numeric segment), but the field array
21+
// is `steps.0.items` — so isNameInFieldArray must check deeper parents too.
22+
expect(
23+
isNameInFieldArray(new Set(['steps.0.items']), 'steps.0.items.2.name'),
24+
).toBeTruthy();
25+
expect(
26+
isNameInFieldArray(new Set(['steps.0.items']), 'steps.0.items.0.value'),
27+
).toBeTruthy();
28+
// Deeper nesting: `a.0.b.1.items`
29+
expect(
30+
isNameInFieldArray(new Set(['a.0.b.1.items']), 'a.0.b.1.items.3.name'),
31+
).toBeTruthy();
32+
// Should still return false when there's no actual match
33+
expect(
34+
isNameInFieldArray(new Set(['steps.0.other']), 'steps.0.items.2.name'),
35+
).toBeFalsy();
36+
// Original top-level behavior should still work
37+
expect(isNameInFieldArray(new Set(['items']), 'items.2.name')).toBeTruthy();
38+
});
39+
});

src/__tests__/useForm/setValue.test.tsx

Lines changed: 0 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -1561,95 +1561,6 @@ describe('setValue', () => {
15611561
expect(valueNotifications).toHaveLength(0);
15621562
});
15631563

1564-
it('should re-render useFieldArray fields when setValue targets a nested array path - issue #13260', () => {
1565-
type FormValues = {
1566-
items: { value: number }[];
1567-
};
1568-
1569-
const App = () => {
1570-
const { control, setValue } = useForm<FormValues>({
1571-
defaultValues: {
1572-
items: [{ value: 0 }, { value: 0 }, { value: 0 }],
1573-
},
1574-
});
1575-
const { fields } = useFieldArray({ control, name: 'items' });
1576-
1577-
return (
1578-
<div>
1579-
{fields.map((field, index) => (
1580-
<span key={field.id} data-testid={`item-${index}`}>
1581-
{field.value}
1582-
</span>
1583-
))}
1584-
<button
1585-
type="button"
1586-
onClick={() => setValue('items.1.value', 42, { shouldDirty: true })}
1587-
>
1588-
update
1589-
</button>
1590-
</div>
1591-
);
1592-
};
1593-
1594-
render(<App />);
1595-
1596-
expect(screen.getByTestId('item-1').textContent).toBe('0');
1597-
1598-
act(() => {
1599-
fireEvent.click(screen.getByRole('button', { name: /update/i }));
1600-
});
1601-
1602-
expect(screen.getByTestId('item-1').textContent).toBe('42');
1603-
});
1604-
1605-
it('should re-render all matching field arrays when setValue targets nested field-array path', () => {
1606-
type FormValues = {
1607-
items: { subitems: { value: number }[] }[];
1608-
};
1609-
1610-
const App = () => {
1611-
const { control, setValue } = useForm<FormValues>({
1612-
defaultValues: {
1613-
items: [{ subitems: [{ value: 0 }, { value: 0 }] }],
1614-
},
1615-
});
1616-
const { fields: itemFields } = useFieldArray({ control, name: 'items' });
1617-
const { fields: subItemFields } = useFieldArray({
1618-
control,
1619-
name: 'items.0.subitems',
1620-
});
1621-
1622-
return (
1623-
<div>
1624-
<span data-testid="item-nested-value">
1625-
{itemFields[0]?.subitems?.[1]?.value}
1626-
</span>
1627-
<span data-testid="subitem-value">{subItemFields[1]?.value}</span>
1628-
<button
1629-
type="button"
1630-
onClick={() =>
1631-
setValue('items.0.subitems.1.value', 7, { shouldDirty: true })
1632-
}
1633-
>
1634-
update nested
1635-
</button>
1636-
</div>
1637-
);
1638-
};
1639-
1640-
render(<App />);
1641-
1642-
expect(screen.getByTestId('item-nested-value').textContent).toBe('0');
1643-
expect(screen.getByTestId('subitem-value').textContent).toBe('0');
1644-
1645-
act(() => {
1646-
fireEvent.click(screen.getByRole('button', { name: /update nested/i }));
1647-
});
1648-
1649-
expect(screen.getByTestId('item-nested-value').textContent).toBe('7');
1650-
expect(screen.getByTestId('subitem-value').textContent).toBe('7');
1651-
});
1652-
16531564
it('should mark field as dirty when updating array of objects with shouldDirty true', () => {
16541565
type IData = {
16551566
data: { id: number; name: string }[];

src/logic/createFormControl.ts

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -83,14 +83,14 @@ import unset from '../utils/unset';
8383
import generateWatchOutput from './generateWatchOutput';
8484
import getDirtyFields from './getDirtyFields';
8585
import getEventValue from './getEventValue';
86-
import getFieldArrayParentNames from './getFieldArrayParentNames';
8786
import getFieldValue from './getFieldValue';
8887
import getFieldValueAs from './getFieldValueAs';
8988
import getResolverOptions from './getResolverOptions';
9089
import getRuleValue from './getRuleValue';
9190
import getValidationModes from './getValidationModes';
9291
import hasPromiseValidation from './hasPromiseValidation';
9392
import hasValidation from './hasValidation';
93+
import isNameInFieldArray from './isNameInFieldArray';
9494
import isWatched from './isWatched';
9595
import iterateFieldsByAction from './iterateFieldsByAction';
9696
import schemaErrorLookup from './schemaErrorLookup';
@@ -916,12 +916,6 @@ export function createFormControl<
916916
const watched = isWatched(name, _names);
917917
const values = skipClone ? _formValues : cloneObject(_formValues);
918918

919-
if (!isFieldArray) {
920-
for (const arrayName of getFieldArrayParentNames(_names.array, name)) {
921-
_subjects.array.next({ name: arrayName, values });
922-
}
923-
}
924-
925919
_subjects.state.next({
926920
...(watched && _formState),
927921
name: _state.mount || watched ? name : undefined,
@@ -1477,10 +1471,7 @@ export function createFormControl<
14771471
}
14781472

14791473
(_options.shouldUnregister || options.shouldUnregister) &&
1480-
!(
1481-
getFieldArrayParentNames(_names.array, name).length &&
1482-
_state.action
1483-
) &&
1474+
!(isNameInFieldArray(_names.array, name) && _state.action) &&
14841475
_names.unMount.add(name);
14851476
}
14861477
},

src/logic/getFieldArrayParentNames.ts

Lines changed: 0 additions & 13 deletions
This file was deleted.

src/logic/isNameInFieldArray.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import type { InternalFieldName } from '../types';
2+
3+
export default (names: Set<InternalFieldName>, name: InternalFieldName) =>
4+
name
5+
.split('.')
6+
.some(
7+
(part, index, arr) =>
8+
!isNaN(Number(part)) && names.has(arr.slice(0, index).join('.')),
9+
);

src/useController.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import React from 'react';
22

33
import getEventValue from './logic/getEventValue';
4-
import getFieldArrayParentNames from './logic/getFieldArrayParentNames';
4+
import isNameInFieldArray from './logic/isNameInFieldArray';
55
import cloneObject from './utils/cloneObject';
66
import get from './utils/get';
77
import isBoolean from './utils/isBoolean';
@@ -67,8 +67,7 @@ export function useController<
6767
defaultValue,
6868
exact = true,
6969
} = props;
70-
const isArrayField = !!getFieldArrayParentNames(control._names.array, name)
71-
.length;
70+
const isArrayField = isNameInFieldArray(control._names.array, name);
7271

7372
const defaultValueMemo = React.useMemo(
7473
() =>

0 commit comments

Comments
 (0)