fix(ui): use consistent row ids when duplicating array and block rows (#13679)

Fixes #13653.

Duplicating array rows causes phantom rows to appear. This is because
when duplicate the row locally, we use inconsistent row IDs, e.g. the
`array.rows[0].id` does not match its `array.0.id` counterpart. This
causes form state to lose the reference to the existing row, which the
server interprets as new row as of #13551.

Before:


https://github.com/user-attachments/assets/9f7efc59-ebd9-4fbb-b643-c22d4d3140a3

After:


https://github.com/user-attachments/assets/188db823-4ee5-4757-8b89-751c8d978ad9

---
- To see the specific tasks where the Asana app for GitHub is being
used, see below:
  - https://app.asana.com/0/0/1211210023936585
This commit is contained in:
Jacob Fletcher
2025-09-03 14:29:39 -04:00
committed by GitHub
parent be47f65b7c
commit b8d7ccb4dc
14 changed files with 349 additions and 190 deletions

View File

@@ -149,7 +149,7 @@ export interface Config {
'payload-migrations': PayloadMigrationsSelect<false> | PayloadMigrationsSelect<true>;
};
db: {
defaultIDType: number;
defaultIDType: string;
};
globals: {};
globalsSelect: {};
@@ -215,7 +215,7 @@ export interface LocalizedTextReference2 {
* via the `definition` "users".
*/
export interface User {
id: number;
id: string;
canViewConditionalField?: boolean | null;
updatedAt: string;
createdAt: string;
@@ -240,7 +240,7 @@ export interface User {
* via the `definition` "select-versions-fields".
*/
export interface SelectVersionsField {
id: number;
id: string;
hasMany?: ('a' | 'b' | 'c' | 'd')[] | null;
array?:
| {
@@ -265,7 +265,7 @@ export interface SelectVersionsField {
* via the `definition` "array-fields".
*/
export interface ArrayField {
id: number;
id: string;
title?: string | null;
items: {
text: string;
@@ -369,7 +369,7 @@ export interface ArrayField {
* via the `definition` "block-fields".
*/
export interface BlockField {
id: number;
id: string;
blocks: (ContentBlock | NoBlockname | NumberBlock | SubBlocksBlock | TabsBlock)[];
duplicate: (ContentBlock | NoBlockname | NumberBlock | SubBlocksBlock | TabsBlock)[];
collapsedByDefaultBlocks: (
@@ -500,7 +500,7 @@ export interface BlockField {
| null;
relationshipBlocks?:
| {
relationship?: (number | null) | TextField;
relationship?: (string | null) | TextField;
id?: string | null;
blockName?: string | null;
blockType: 'relationships';
@@ -697,7 +697,7 @@ export interface LocalizedTabsBlock {
* via the `definition` "text-fields".
*/
export interface TextField {
id: number;
id: string;
text: string;
hiddenTextField?: string | null;
/**
@@ -749,7 +749,7 @@ export interface TextField {
* via the `definition` "checkbox-fields".
*/
export interface CheckboxField {
id: number;
id: string;
checkbox: boolean;
checkboxNotRequired?: boolean | null;
updatedAt: string;
@@ -760,7 +760,7 @@ export interface CheckboxField {
* via the `definition` "code-fields".
*/
export interface CodeField {
id: number;
id: string;
javascript?: string | null;
typescript?: string | null;
json?: string | null;
@@ -775,7 +775,7 @@ export interface CodeField {
* via the `definition` "collapsible-fields".
*/
export interface CollapsibleField {
id: number;
id: string;
text: string;
group: {
textWithinGroup?: string | null;
@@ -808,7 +808,7 @@ export interface CollapsibleField {
* via the `definition` "conditional-logic".
*/
export interface ConditionalLogic {
id: number;
id: string;
text: string;
toggleField?: boolean | null;
fieldWithDocIDCondition?: string | null;
@@ -922,7 +922,7 @@ export interface CustomRowId {
* via the `definition` "date-fields".
*/
export interface DateField {
id: number;
id: string;
default: string;
timeOnly?: string | null;
timeOnlyWithMiliseconds?: string | null;
@@ -967,7 +967,7 @@ export interface DateField {
* via the `definition` "email-fields".
*/
export interface EmailField {
id: number;
id: string;
email: string;
localizedEmail?: string | null;
emailWithAutocomplete?: string | null;
@@ -992,7 +992,7 @@ export interface EmailField {
* via the `definition` "radio-fields".
*/
export interface RadioField {
id: number;
id: string;
radio?: ('one' | 'two' | 'three') | null;
radioWithJsxLabelOption?: ('one' | 'two' | 'three') | null;
updatedAt: string;
@@ -1003,7 +1003,7 @@ export interface RadioField {
* via the `definition` "group-fields".
*/
export interface GroupField {
id: number;
id: string;
/**
* This is a group.
*/
@@ -1085,22 +1085,22 @@ export interface GroupField {
select?: ('one' | 'two')[] | null;
};
localizedGroupRel?: {
email?: (number | null) | EmailField;
email?: (string | null) | EmailField;
};
localizedGroupManyRel?: {
email?: (number | EmailField)[] | null;
email?: (string | EmailField)[] | null;
};
localizedGroupPolyRel?: {
email?: {
relationTo: 'email-fields';
value: number | EmailField;
value: string | EmailField;
} | null;
};
localizedGroupPolyHasManyRel?: {
email?:
| {
relationTo: 'email-fields';
value: number | EmailField;
value: string | EmailField;
}[]
| null;
};
@@ -1154,30 +1154,30 @@ export interface RowField {
* via the `definition` "indexed-fields".
*/
export interface IndexedField {
id: number;
id: string;
text: string;
uniqueText?: string | null;
uniqueRelationship?: (number | null) | TextField;
uniqueHasManyRelationship?: (number | TextField)[] | null;
uniqueHasManyRelationship_2?: (number | TextField)[] | null;
uniqueRelationship?: (string | null) | TextField;
uniqueHasManyRelationship?: (string | TextField)[] | null;
uniqueHasManyRelationship_2?: (string | TextField)[] | null;
uniquePolymorphicRelationship?: {
relationTo: 'text-fields';
value: number | TextField;
value: string | TextField;
} | null;
uniquePolymorphicRelationship_2?: {
relationTo: 'text-fields';
value: number | TextField;
value: string | TextField;
} | null;
uniqueHasManyPolymorphicRelationship?:
| {
relationTo: 'text-fields';
value: number | TextField;
value: string | TextField;
}[]
| null;
uniqueHasManyPolymorphicRelationship_2?:
| {
relationTo: 'text-fields';
value: number | TextField;
value: string | TextField;
}[]
| null;
uniqueRequiredText: string;
@@ -1213,7 +1213,7 @@ export interface IndexedField {
* via the `definition` "json-fields".
*/
export interface JsonField {
id: number;
id: string;
json?: {
array?: {
object?: {
@@ -1254,7 +1254,7 @@ export interface JsonField {
* via the `definition` "number-fields".
*/
export interface NumberField {
id: number;
id: string;
number?: number | null;
min?: number | null;
max?: number | null;
@@ -1289,7 +1289,7 @@ export interface NumberField {
* via the `definition` "point-fields".
*/
export interface PointField {
id: number;
id: string;
/**
* @minItems 2
* @maxItems 2
@@ -1320,83 +1320,83 @@ export interface PointField {
* via the `definition` "relationship-fields".
*/
export interface RelationshipField {
id: number;
id: string;
text?: string | null;
relationship:
| {
relationTo: 'text-fields';
value: number | TextField;
value: string | TextField;
}
| {
relationTo: 'array-fields';
value: number | ArrayField;
value: string | ArrayField;
};
relationHasManyPolymorphic?:
| (
| {
relationTo: 'text-fields';
value: number | TextField;
value: string | TextField;
}
| {
relationTo: 'array-fields';
value: number | ArrayField;
value: string | ArrayField;
}
)[]
| null;
relationToSelf?: (number | null) | RelationshipField;
relationToSelfSelectOnly?: (number | null) | RelationshipField;
relationWithAllowCreateToFalse?: (number | null) | User;
relationWithAllowEditToFalse?: (number | null) | User;
relationWithDynamicDefault?: (number | null) | User;
relationToSelf?: (string | null) | RelationshipField;
relationToSelfSelectOnly?: (string | null) | RelationshipField;
relationWithAllowCreateToFalse?: (string | null) | User;
relationWithAllowEditToFalse?: (string | null) | User;
relationWithDynamicDefault?: (string | null) | User;
relationHasManyWithDynamicDefault?: {
relationTo: 'users';
value: number | User;
value: string | User;
} | null;
relationshipWithMin?: (number | TextField)[] | null;
relationshipWithMax?: (number | TextField)[] | null;
relationshipHasMany?: (number | TextField)[] | null;
relationshipWithMin?: (string | TextField)[] | null;
relationshipWithMax?: (string | TextField)[] | null;
relationshipHasMany?: (string | TextField)[] | null;
array?:
| {
relationship?: (number | null) | TextField;
relationship?: (string | null) | TextField;
id?: string | null;
}[]
| null;
relationshipWithMinRows?:
| {
relationTo: 'text-fields';
value: number | TextField;
value: string | TextField;
}[]
| null;
relationToRow?: (string | null) | RowField;
relationToRowMany?: (string | RowField)[] | null;
relationshipDrawer?: (number | null) | TextField;
relationshipDrawerReadOnly?: (number | null) | TextField;
relationshipDrawer?: (string | null) | TextField;
relationshipDrawerReadOnly?: (string | null) | TextField;
polymorphicRelationshipDrawer?:
| ({
relationTo: 'text-fields';
value: number | TextField;
value: string | TextField;
} | null)
| ({
relationTo: 'array-fields';
value: number | ArrayField;
value: string | ArrayField;
} | null);
relationshipDrawerHasMany?: (number | TextField)[] | null;
relationshipDrawerHasMany?: (string | TextField)[] | null;
relationshipDrawerHasManyPolymorphic?:
| (
| {
relationTo: 'text-fields';
value: number | TextField;
value: string | TextField;
}
| {
relationTo: 'array-fields';
value: number | ArrayField;
value: string | ArrayField;
}
)[]
| null;
relationshipDrawerWithAllowCreateFalse?: (number | null) | TextField;
relationshipDrawerWithAllowCreateFalse?: (string | null) | TextField;
relationshipDrawerWithFilterOptions?: {
relationTo: 'text-fields';
value: number | TextField;
value: string | TextField;
} | null;
updatedAt: string;
createdAt: string;
@@ -1406,7 +1406,7 @@ export interface RelationshipField {
* via the `definition` "select-fields".
*/
export interface SelectField {
id: number;
id: string;
select?: ('one' | 'two' | 'three') | null;
selectReadOnly?: ('one' | 'two' | 'three') | null;
selectHasMany?: ('one' | 'two' | 'three' | 'four' | 'five' | 'six')[] | null;
@@ -1436,7 +1436,7 @@ export interface SelectField {
* via the `definition` "tabs-fields-2".
*/
export interface TabsFields2 {
id: number;
id: string;
tabsInArray?:
| {
text?: string | null;
@@ -1454,7 +1454,7 @@ export interface TabsFields2 {
* via the `definition` "tabs-fields".
*/
export interface TabsField {
id: number;
id: string;
/**
* This should not collapse despite there being many tabs pushing the main fields open.
*/
@@ -1556,9 +1556,9 @@ export interface TabWithName {
* via the `definition` "uploads".
*/
export interface Upload {
id: number;
id: string;
text?: string | null;
media?: (number | null) | Upload;
media?: (string | null) | Upload;
updatedAt: string;
createdAt: string;
url?: string | null;
@@ -1576,9 +1576,9 @@ export interface Upload {
* via the `definition` "uploads2".
*/
export interface Uploads2 {
id: number;
id: string;
text?: string | null;
media?: (number | null) | Uploads2;
media?: (string | null) | Uploads2;
updatedAt: string;
createdAt: string;
url?: string | null;
@@ -1596,8 +1596,8 @@ export interface Uploads2 {
* via the `definition` "uploads3".
*/
export interface Uploads3 {
id: number;
media?: (number | null) | Uploads3;
id: string;
media?: (string | null) | Uploads3;
updatedAt: string;
createdAt: string;
url?: string | null;
@@ -1615,9 +1615,9 @@ export interface Uploads3 {
* via the `definition` "uploads-multi".
*/
export interface UploadsMulti {
id: number;
id: string;
text?: string | null;
media?: (number | Upload)[] | null;
media?: (string | Upload)[] | null;
updatedAt: string;
createdAt: string;
}
@@ -1626,16 +1626,16 @@ export interface UploadsMulti {
* via the `definition` "uploads-poly".
*/
export interface UploadsPoly {
id: number;
id: string;
text?: string | null;
media?:
| ({
relationTo: 'uploads';
value: number | Upload;
value: string | Upload;
} | null)
| ({
relationTo: 'uploads2';
value: number | Uploads2;
value: string | Uploads2;
} | null);
updatedAt: string;
createdAt: string;
@@ -1645,17 +1645,17 @@ export interface UploadsPoly {
* via the `definition` "uploads-multi-poly".
*/
export interface UploadsMultiPoly {
id: number;
id: string;
text?: string | null;
media?:
| (
| {
relationTo: 'uploads';
value: number | Upload;
value: string | Upload;
}
| {
relationTo: 'uploads2';
value: number | Uploads2;
value: string | Uploads2;
}
)[]
| null;
@@ -1667,11 +1667,11 @@ export interface UploadsMultiPoly {
* via the `definition` "uploads-restricted".
*/
export interface UploadsRestricted {
id: number;
id: string;
text?: string | null;
uploadWithoutRestriction?: (number | null) | Upload;
uploadWithAllowCreateFalse?: (number | null) | Upload;
uploadMultipleWithAllowCreateFalse?: (number | Upload)[] | null;
uploadWithoutRestriction?: (string | null) | Upload;
uploadWithAllowCreateFalse?: (string | null) | Upload;
uploadMultipleWithAllowCreateFalse?: (string | Upload)[] | null;
updatedAt: string;
createdAt: string;
}
@@ -1680,7 +1680,7 @@ export interface UploadsRestricted {
* via the `definition` "ui-fields".
*/
export interface UiField {
id: number;
id: string;
text: string;
updatedAt: string;
createdAt: string;
@@ -1690,39 +1690,39 @@ export interface UiField {
* via the `definition` "payload-locked-documents".
*/
export interface PayloadLockedDocument {
id: number;
id: string;
document?:
| ({
relationTo: 'users';
value: number | User;
value: string | User;
} | null)
| ({
relationTo: 'select-versions-fields';
value: number | SelectVersionsField;
value: string | SelectVersionsField;
} | null)
| ({
relationTo: 'array-fields';
value: number | ArrayField;
value: string | ArrayField;
} | null)
| ({
relationTo: 'block-fields';
value: number | BlockField;
value: string | BlockField;
} | null)
| ({
relationTo: 'checkbox-fields';
value: number | CheckboxField;
value: string | CheckboxField;
} | null)
| ({
relationTo: 'code-fields';
value: number | CodeField;
value: string | CodeField;
} | null)
| ({
relationTo: 'collapsible-fields';
value: number | CollapsibleField;
value: string | CollapsibleField;
} | null)
| ({
relationTo: 'conditional-logic';
value: number | ConditionalLogic;
value: string | ConditionalLogic;
} | null)
| ({
relationTo: 'custom-id';
@@ -1730,27 +1730,27 @@ export interface PayloadLockedDocument {
} | null)
| ({
relationTo: 'custom-tab-id';
value: number | CustomTabId;
value: string | CustomTabId;
} | null)
| ({
relationTo: 'custom-row-id';
value: number | CustomRowId;
value: string | CustomRowId;
} | null)
| ({
relationTo: 'date-fields';
value: number | DateField;
value: string | DateField;
} | null)
| ({
relationTo: 'email-fields';
value: number | EmailField;
value: string | EmailField;
} | null)
| ({
relationTo: 'radio-fields';
value: number | RadioField;
value: string | RadioField;
} | null)
| ({
relationTo: 'group-fields';
value: number | GroupField;
value: string | GroupField;
} | null)
| ({
relationTo: 'row-fields';
@@ -1758,76 +1758,76 @@ export interface PayloadLockedDocument {
} | null)
| ({
relationTo: 'indexed-fields';
value: number | IndexedField;
value: string | IndexedField;
} | null)
| ({
relationTo: 'json-fields';
value: number | JsonField;
value: string | JsonField;
} | null)
| ({
relationTo: 'number-fields';
value: number | NumberField;
value: string | NumberField;
} | null)
| ({
relationTo: 'point-fields';
value: number | PointField;
value: string | PointField;
} | null)
| ({
relationTo: 'relationship-fields';
value: number | RelationshipField;
value: string | RelationshipField;
} | null)
| ({
relationTo: 'select-fields';
value: number | SelectField;
value: string | SelectField;
} | null)
| ({
relationTo: 'tabs-fields-2';
value: number | TabsFields2;
value: string | TabsFields2;
} | null)
| ({
relationTo: 'tabs-fields';
value: number | TabsField;
value: string | TabsField;
} | null)
| ({
relationTo: 'text-fields';
value: number | TextField;
value: string | TextField;
} | null)
| ({
relationTo: 'uploads';
value: number | Upload;
value: string | Upload;
} | null)
| ({
relationTo: 'uploads2';
value: number | Uploads2;
value: string | Uploads2;
} | null)
| ({
relationTo: 'uploads3';
value: number | Uploads3;
value: string | Uploads3;
} | null)
| ({
relationTo: 'uploads-multi';
value: number | UploadsMulti;
value: string | UploadsMulti;
} | null)
| ({
relationTo: 'uploads-poly';
value: number | UploadsPoly;
value: string | UploadsPoly;
} | null)
| ({
relationTo: 'uploads-multi-poly';
value: number | UploadsMultiPoly;
value: string | UploadsMultiPoly;
} | null)
| ({
relationTo: 'uploads-restricted';
value: number | UploadsRestricted;
value: string | UploadsRestricted;
} | null)
| ({
relationTo: 'ui-fields';
value: number | UiField;
value: string | UiField;
} | null);
globalSlug?: string | null;
user: {
relationTo: 'users';
value: number | User;
value: string | User;
};
updatedAt: string;
createdAt: string;
@@ -1837,10 +1837,10 @@ export interface PayloadLockedDocument {
* via the `definition` "payload-preferences".
*/
export interface PayloadPreference {
id: number;
id: string;
user: {
relationTo: 'users';
value: number | User;
value: string | User;
};
key?: string | null;
value?:
@@ -1860,7 +1860,7 @@ export interface PayloadPreference {
* via the `definition` "payload-migrations".
*/
export interface PayloadMigration {
id: number;
id: string;
name?: string | null;
batch?: number | null;
updatedAt: string;