fix(ui): copyToLocale should not pass any id's to avoid duplicates (#11887)

### What?
Using the `Copy To Locale` function causes validation errors on content
with `id` fields in postgres, since these should be unique.

```
key not found: error:valueMustBeUnique
key not found: error:followingFieldsInvalid
[13:11:29] ERROR: There was an error copying data from "en" to "de"
    err: {
      "type": "ValidationError",
      "message": "error:followingFieldsInvalid id",
      "stack":
          ValidationError: error:followingFieldsInvalid id
```

### Why?
In `packages/ui/src/utilities/copyDataFromLocale.ts` we are passing all
data from `fromLocaleData` including the `id` fields, which causes
duplicates on fields with unique id's like `Blocks` and `Arrays`.

### How?
To resolve this i implemented a function that recursively remove any
`id` field on the passed data.

### Fixes
- https://github.com/payloadcms/payload/issues/10684
- https://discord.com/channels/967097582721572934/1351497930984521800

---------

Co-authored-by: Jessica Chowdhury <jessica@trbl.design>
This commit is contained in:
Bjørn Nyborg
2025-04-29 10:23:40 +02:00
committed by GitHub
parent caae5986f5
commit 34ead72c85
2 changed files with 42 additions and 5 deletions

View File

@@ -183,6 +183,17 @@ function mergeData(
return toLocaleData
}
function removeIds(data: Data): Data {
if (Array.isArray(data)) {
return data.map(removeIds)
}
if (typeof data === 'object' && data !== null) {
const { id: _id, ...rest } = data
return Object.fromEntries(Object.entries(rest).map(([key, value]) => [key, removeIds(value)]))
}
return data
}
export const copyDataFromLocaleHandler = async (args: CopyDataFromLocaleArgs) => {
const { req } = args
@@ -288,7 +299,8 @@ export const copyDataFromLocale = async (args: CopyDataFromLocaleArgs) => {
throw new Error(`Error fetching data from locale "${toLocale}"`)
}
const { id, ...fromLocaleDataWithoutID } = fromLocaleData.value
const fromLocaleDataWithoutID = removeIds(fromLocaleData.value)
const toLocaleDataWithoutID = removeIds(toLocaleData.value)
return globalSlug
? await payload.updateGlobal({
@@ -296,8 +308,8 @@ export const copyDataFromLocale = async (args: CopyDataFromLocaleArgs) => {
data: overrideData
? fromLocaleDataWithoutID
: mergeData(
fromLocaleData.value,
toLocaleData.value,
fromLocaleDataWithoutID,
toLocaleDataWithoutID,
globals[globalSlug].config.fields,
req,
false,
@@ -313,8 +325,8 @@ export const copyDataFromLocale = async (args: CopyDataFromLocaleArgs) => {
data: overrideData
? fromLocaleDataWithoutID
: mergeData(
fromLocaleData.value,
toLocaleData.value,
fromLocaleDataWithoutID,
toLocaleDataWithoutID,
collections[collectionSlug].config.fields,
req,
false,