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 { useDocumentInfo } from '../../providers/DocumentInfo/index.js'
|
||||
import { FieldDescription } from '../FieldDescription/index.js'
|
||||
import { FieldError } from '../FieldError/index.js'
|
||||
import { FieldLabel } from '../FieldLabel/index.js'
|
||||
import { fieldBaseClass } from '../index.js'
|
||||
|
||||
@@ -137,10 +138,13 @@ const JoinFieldComponent: JoinFieldClientComponent = (props) => {
|
||||
|
||||
const { config, getEntityConfig } = useConfig()
|
||||
|
||||
const { customComponents: { AfterInput, BeforeInput, Description, Label } = {}, value } =
|
||||
useField<PaginatedDocs>({
|
||||
path,
|
||||
})
|
||||
const {
|
||||
customComponents: { AfterInput, BeforeInput, Description, Error, Label } = {},
|
||||
showError,
|
||||
value,
|
||||
} = useField<PaginatedDocs>({
|
||||
path,
|
||||
})
|
||||
|
||||
const filterOptions: null | Where = useMemo(() => {
|
||||
if (!docID) {
|
||||
@@ -189,9 +193,13 @@ const JoinFieldComponent: JoinFieldClientComponent = (props) => {
|
||||
|
||||
return (
|
||||
<div
|
||||
className={[fieldBaseClass, 'join'].filter(Boolean).join(' ')}
|
||||
className={[fieldBaseClass, showError && 'error', 'join'].filter(Boolean).join(' ')}
|
||||
id={`field-${path?.replace(/\./g, '__')}`}
|
||||
>
|
||||
<RenderCustomComponent
|
||||
CustomComponent={Error}
|
||||
Fallback={<FieldError path={path} showError={showError} />}
|
||||
/>
|
||||
<RelationshipTable
|
||||
AfterInput={AfterInput}
|
||||
allowCreate={typeof docID !== 'undefined' && allowCreate}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import type { CollectionConfig } from 'payload'
|
||||
|
||||
import { ValidationError } from 'payload'
|
||||
|
||||
import { categoriesSlug, hiddenPostsSlug, postsSlug } from '../shared.js'
|
||||
import { singularSlug } from './Singular.js'
|
||||
|
||||
@@ -160,5 +162,33 @@ export const Categories: CollectionConfig = {
|
||||
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 { navigateToDoc } from '../helpers/e2e/navigateToDoc.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 { 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 dirname = path.dirname(filename)
|
||||
@@ -37,7 +37,7 @@ describe('Join Field', () => {
|
||||
let categoriesURL: AdminUrlUtil
|
||||
let uploadsURL: AdminUrlUtil
|
||||
let categoriesJoinRestrictedURL: AdminUrlUtil
|
||||
let categoryID: string | number
|
||||
let categoryID: number | string
|
||||
|
||||
beforeAll(async ({ browser }, testInfo) => {
|
||||
testInfo.setTimeout(TEST_TIMEOUT_LONG)
|
||||
@@ -474,4 +474,13 @@ describe('Join Field', () => {
|
||||
await page.goto(url.admin + '/create-first-user')
|
||||
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';
|
||||
localizedPolymorphics: 'posts';
|
||||
filtered: 'posts';
|
||||
joinWithError: 'posts';
|
||||
hiddenPosts: 'hidden-posts';
|
||||
singulars: 'singular';
|
||||
};
|
||||
@@ -336,6 +337,11 @@ export interface Category {
|
||||
docs?: (string | Post)[] | null;
|
||||
hasNextPage?: boolean | null;
|
||||
} | null;
|
||||
joinWithError?: {
|
||||
docs?: (string | Post)[] | null;
|
||||
hasNextPage?: boolean | null;
|
||||
} | null;
|
||||
enableErrorOnJoin?: boolean | null;
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
}
|
||||
@@ -730,6 +736,8 @@ export interface CategoriesSelect<T extends boolean = true> {
|
||||
localizedPolymorphics?: T;
|
||||
singulars?: T;
|
||||
filtered?: T;
|
||||
joinWithError?: T;
|
||||
enableErrorOnJoin?: T;
|
||||
updatedAt?: T;
|
||||
createdAt?: T;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user