diff --git a/packages/ui/src/forms/Form/fieldReducer.ts b/packages/ui/src/forms/Form/fieldReducer.ts index 7ed0bc78e..143dbf41a 100644 --- a/packages/ui/src/forms/Form/fieldReducer.ts +++ b/packages/ui/src/forms/Form/fieldReducer.ts @@ -47,6 +47,68 @@ export function fieldReducer(state: FormState, action: FieldAction): FormState { return newState } + case 'ADD_SERVER_ERRORS': { + let newState = { ...state } + + const errorPaths: { fieldErrorPath: string; parentPath: string }[] = [] + + action.errors.forEach(({ field, message }) => { + newState[field] = { + ...(newState[field] || { + initialValue: null, + value: null, + }), + errorMessage: message, + valid: false, + } + + const segments = field.split('.') + if (segments.length > 1) { + errorPaths.push({ + fieldErrorPath: field, + parentPath: segments.slice(0, segments.length - 1).join('.'), + }) + } + }) + + newState = Object.entries(newState).reduce((acc, [path, fieldState]) => { + const fieldErrorPaths = errorPaths.reduce((errorACC, { fieldErrorPath, parentPath }) => { + if (parentPath.startsWith(path)) { + errorACC.push(fieldErrorPath) + } + return errorACC + }, []) + + let changed = false + + if (fieldErrorPaths.length > 0) { + const newErrorPaths = Array.isArray(fieldState.errorPaths) ? fieldState.errorPaths : [] + + fieldErrorPaths.forEach((fieldErrorPath) => { + if (!newErrorPaths.includes(fieldErrorPath)) { + newErrorPaths.push(fieldErrorPath) + changed = true + } + }) + + if (changed) { + acc[path] = { + ...fieldState, + errorPaths: newErrorPaths, + } + } + } + + if (!changed) { + acc[path] = fieldState + } + + return acc + }, {}) + + return newState + } + case 'UPDATE': { const newField = Object.entries(action).reduce( (field, [key, value]) => { diff --git a/packages/ui/src/forms/Form/index.tsx b/packages/ui/src/forms/Form/index.tsx index 7ab6ddacd..23d81406d 100644 --- a/packages/ui/src/forms/Form/index.tsx +++ b/packages/ui/src/forms/Form/index.tsx @@ -317,14 +317,9 @@ export const Form: React.FC = (props) => { [[], []], ) - fieldErrors.forEach((err) => { - dispatchFields({ - type: 'UPDATE', - ...(contextRef.current?.fields?.[err.field] || {}), - errorMessage: err.message, - path: err.field, - valid: false, - }) + dispatchFields({ + type: 'ADD_SERVER_ERRORS', + errors: fieldErrors, }) nonFieldErrors.forEach((err) => { diff --git a/packages/ui/src/forms/Form/types.ts b/packages/ui/src/forms/Form/types.ts index ec48eb4a4..fbaef4366 100644 --- a/packages/ui/src/forms/Form/types.ts +++ b/packages/ui/src/forms/Form/types.ts @@ -129,6 +129,14 @@ export type MOVE_ROW = { type: 'MOVE_ROW' } +export type ADD_SERVER_ERRORS = { + errors: { + field: string + message: string + }[] + type: 'ADD_SERVER_ERRORS' +} + export type SET_ROW_COLLAPSED = { collapsed: boolean path: string @@ -146,6 +154,7 @@ export type SET_ALL_ROWS_COLLAPSED = { export type FieldAction = | ADD_ROW + | ADD_SERVER_ERRORS | DUPLICATE_ROW | MODIFY_CONDITION | MOVE_ROW diff --git a/test/fields/e2e.spec.ts b/test/fields/e2e.spec.ts index 291a9c46c..c40595acc 100644 --- a/test/fields/e2e.spec.ts +++ b/test/fields/e2e.spec.ts @@ -254,7 +254,7 @@ describe('fields', () => { }) // TODO - This test is flaky. Rarely, but sometimes it randomly fails. - test.skip('should display unique constraint error in ui', async () => { + test('should display unique constraint error in ui', async () => { const uniqueText = 'uniqueText' await payload.create({ collection: 'indexed-fields', diff --git a/test/tsconfig.json b/test/tsconfig.json index 209f12718..1c52f71df 100644 --- a/test/tsconfig.json +++ b/test/tsconfig.json @@ -40,8 +40,12 @@ "@payloadcms/ui/scss": ["../packages/ui/src/scss.scss"], "@payloadcms/ui/scss/app.scss": ["../packages/ui/src/scss/app.scss"], "payload/types": ["../packages/payload/src/exports/types/index.ts"], - "@payloadcms/next/*": ["../packages/next/src/*"], - "@payloadcms/next": ["../packages/next/src/exports/*"], + "@payloadcms/next/*": [ + "./packages/next/src/exports/*" + ], + "@payloadcms/next": [ + "./packages/next/src/exports/*" + ], "@payload-config": ["./_community/config.ts"] } },