fix: join field does not show validation error (#11170)
### What? Assuming you have a hook in your collection that is looking for certain conditions to be met related to the join field. The way you would prevent it is to throw a `new ValidationError()` with errors containing the path of the field. Previously, the error message for the field would not show in the admin UI at all. ### Why? Users need to be able to see any custom error messages for joins field in the UI so they can address the issue. ### How? Adds an error class and display the FieldError in the Join field in the UI component.
This commit is contained in:
@@ -20,6 +20,7 @@ import { withCondition } from '../../forms/withCondition/index.js'
|
|||||||
import { useConfig } from '../../providers/Config/index.js'
|
import { useConfig } from '../../providers/Config/index.js'
|
||||||
import { useDocumentInfo } from '../../providers/DocumentInfo/index.js'
|
import { useDocumentInfo } from '../../providers/DocumentInfo/index.js'
|
||||||
import { FieldDescription } from '../FieldDescription/index.js'
|
import { FieldDescription } from '../FieldDescription/index.js'
|
||||||
|
import { FieldError } from '../FieldError/index.js'
|
||||||
import { FieldLabel } from '../FieldLabel/index.js'
|
import { FieldLabel } from '../FieldLabel/index.js'
|
||||||
import { fieldBaseClass } from '../index.js'
|
import { fieldBaseClass } from '../index.js'
|
||||||
|
|
||||||
@@ -137,8 +138,11 @@ const JoinFieldComponent: JoinFieldClientComponent = (props) => {
|
|||||||
|
|
||||||
const { config, getEntityConfig } = useConfig()
|
const { config, getEntityConfig } = useConfig()
|
||||||
|
|
||||||
const { customComponents: { AfterInput, BeforeInput, Description, Label } = {}, value } =
|
const {
|
||||||
useField<PaginatedDocs>({
|
customComponents: { AfterInput, BeforeInput, Description, Error, Label } = {},
|
||||||
|
showError,
|
||||||
|
value,
|
||||||
|
} = useField<PaginatedDocs>({
|
||||||
path,
|
path,
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -189,9 +193,13 @@ const JoinFieldComponent: JoinFieldClientComponent = (props) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={[fieldBaseClass, 'join'].filter(Boolean).join(' ')}
|
className={[fieldBaseClass, showError && 'error', 'join'].filter(Boolean).join(' ')}
|
||||||
id={`field-${path?.replace(/\./g, '__')}`}
|
id={`field-${path?.replace(/\./g, '__')}`}
|
||||||
>
|
>
|
||||||
|
<RenderCustomComponent
|
||||||
|
CustomComponent={Error}
|
||||||
|
Fallback={<FieldError path={path} showError={showError} />}
|
||||||
|
/>
|
||||||
<RelationshipTable
|
<RelationshipTable
|
||||||
AfterInput={AfterInput}
|
AfterInput={AfterInput}
|
||||||
allowCreate={typeof docID !== 'undefined' && allowCreate}
|
allowCreate={typeof docID !== 'undefined' && allowCreate}
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import type { CollectionConfig } from 'payload'
|
import type { CollectionConfig } from 'payload'
|
||||||
|
|
||||||
|
import { ValidationError } from 'payload'
|
||||||
|
|
||||||
import { categoriesSlug, hiddenPostsSlug, postsSlug } from '../shared.js'
|
import { categoriesSlug, hiddenPostsSlug, postsSlug } from '../shared.js'
|
||||||
import { singularSlug } from './Singular.js'
|
import { singularSlug } from './Singular.js'
|
||||||
|
|
||||||
@@ -160,5 +162,33 @@ export const Categories: CollectionConfig = {
|
|||||||
isFiltered: { not_equals: true },
|
isFiltered: { not_equals: true },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'joinWithError',
|
||||||
|
type: 'join',
|
||||||
|
collection: postsSlug,
|
||||||
|
on: 'category',
|
||||||
|
hooks: {
|
||||||
|
beforeValidate: [
|
||||||
|
({ data }) => {
|
||||||
|
if (data?.enableErrorOnJoin) {
|
||||||
|
throw new ValidationError({
|
||||||
|
collection: 'categories',
|
||||||
|
errors: [
|
||||||
|
{
|
||||||
|
message: 'enableErrorOnJoin is true',
|
||||||
|
path: 'joinWithError',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'enableErrorOnJoin',
|
||||||
|
type: 'checkbox',
|
||||||
|
defaultValue: false,
|
||||||
|
},
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,10 +18,10 @@ import {
|
|||||||
import { AdminUrlUtil } from '../helpers/adminUrlUtil.js'
|
import { AdminUrlUtil } from '../helpers/adminUrlUtil.js'
|
||||||
import { navigateToDoc } from '../helpers/e2e/navigateToDoc.js'
|
import { navigateToDoc } from '../helpers/e2e/navigateToDoc.js'
|
||||||
import { initPayloadE2ENoConfig } from '../helpers/initPayloadE2ENoConfig.js'
|
import { initPayloadE2ENoConfig } from '../helpers/initPayloadE2ENoConfig.js'
|
||||||
import { EXPECT_TIMEOUT, TEST_TIMEOUT_LONG } from '../playwright.config.js'
|
|
||||||
import { categoriesJoinRestrictedSlug, categoriesSlug, postsSlug, uploadsSlug } from './shared.js'
|
|
||||||
import { reInitializeDB } from '../helpers/reInitializeDB.js'
|
import { reInitializeDB } from '../helpers/reInitializeDB.js'
|
||||||
import { RESTClient } from '../helpers/rest.js'
|
import { RESTClient } from '../helpers/rest.js'
|
||||||
|
import { EXPECT_TIMEOUT, TEST_TIMEOUT_LONG } from '../playwright.config.js'
|
||||||
|
import { categoriesJoinRestrictedSlug, categoriesSlug, postsSlug, uploadsSlug } from './shared.js'
|
||||||
|
|
||||||
const filename = fileURLToPath(import.meta.url)
|
const filename = fileURLToPath(import.meta.url)
|
||||||
const dirname = path.dirname(filename)
|
const dirname = path.dirname(filename)
|
||||||
@@ -37,7 +37,7 @@ describe('Join Field', () => {
|
|||||||
let categoriesURL: AdminUrlUtil
|
let categoriesURL: AdminUrlUtil
|
||||||
let uploadsURL: AdminUrlUtil
|
let uploadsURL: AdminUrlUtil
|
||||||
let categoriesJoinRestrictedURL: AdminUrlUtil
|
let categoriesJoinRestrictedURL: AdminUrlUtil
|
||||||
let categoryID: string | number
|
let categoryID: number | string
|
||||||
|
|
||||||
beforeAll(async ({ browser }, testInfo) => {
|
beforeAll(async ({ browser }, testInfo) => {
|
||||||
testInfo.setTimeout(TEST_TIMEOUT_LONG)
|
testInfo.setTimeout(TEST_TIMEOUT_LONG)
|
||||||
@@ -474,4 +474,13 @@ describe('Join Field', () => {
|
|||||||
await page.goto(url.admin + '/create-first-user')
|
await page.goto(url.admin + '/create-first-user')
|
||||||
await expect(page.locator('.field-type.join')).toBeHidden()
|
await expect(page.locator('.field-type.join')).toBeHidden()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('should render error message when ValidationError is thrown', async () => {
|
||||||
|
await navigateToDoc(page, categoriesURL)
|
||||||
|
|
||||||
|
await page.locator('#field-enableErrorOnJoin').click()
|
||||||
|
await page.locator('#action-save').click()
|
||||||
|
|
||||||
|
await expect(page.locator('#field-joinWithError')).toContainText('enableErrorOnJoin is true')
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -51,6 +51,7 @@ export interface Config {
|
|||||||
localizedPolymorphic: 'posts';
|
localizedPolymorphic: 'posts';
|
||||||
localizedPolymorphics: 'posts';
|
localizedPolymorphics: 'posts';
|
||||||
filtered: 'posts';
|
filtered: 'posts';
|
||||||
|
joinWithError: 'posts';
|
||||||
hiddenPosts: 'hidden-posts';
|
hiddenPosts: 'hidden-posts';
|
||||||
singulars: 'singular';
|
singulars: 'singular';
|
||||||
};
|
};
|
||||||
@@ -336,6 +337,11 @@ export interface Category {
|
|||||||
docs?: (string | Post)[] | null;
|
docs?: (string | Post)[] | null;
|
||||||
hasNextPage?: boolean | null;
|
hasNextPage?: boolean | null;
|
||||||
} | null;
|
} | null;
|
||||||
|
joinWithError?: {
|
||||||
|
docs?: (string | Post)[] | null;
|
||||||
|
hasNextPage?: boolean | null;
|
||||||
|
} | null;
|
||||||
|
enableErrorOnJoin?: boolean | null;
|
||||||
updatedAt: string;
|
updatedAt: string;
|
||||||
createdAt: string;
|
createdAt: string;
|
||||||
}
|
}
|
||||||
@@ -730,6 +736,8 @@ export interface CategoriesSelect<T extends boolean = true> {
|
|||||||
localizedPolymorphics?: T;
|
localizedPolymorphics?: T;
|
||||||
singulars?: T;
|
singulars?: T;
|
||||||
filtered?: T;
|
filtered?: T;
|
||||||
|
joinWithError?: T;
|
||||||
|
enableErrorOnJoin?: T;
|
||||||
updatedAt?: T;
|
updatedAt?: T;
|
||||||
createdAt?: T;
|
createdAt?: T;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user