diff --git a/packages/ui/src/fields/Select/index.tsx b/packages/ui/src/fields/Select/index.tsx index dbf00305af..74e99ef478 100644 --- a/packages/ui/src/fields/Select/index.tsx +++ b/packages/ui/src/fields/Select/index.tsx @@ -63,7 +63,7 @@ const SelectField: React.FC = (props) => { width, } = props - const [options] = useState(formatOptions(optionsFromProps)) + const options = React.useMemo(() => formatOptions(optionsFromProps), [optionsFromProps]) const memoizedValidate: ClientValidate = useCallback( (value, validationOptions) => { diff --git a/test/admin/collections/CustomFields/components/CustomSelect.tsx b/test/admin/collections/CustomFields/components/CustomSelect.tsx new file mode 100644 index 0000000000..c333d66adc --- /dev/null +++ b/test/admin/collections/CustomFields/components/CustomSelect.tsx @@ -0,0 +1,46 @@ +'use client' + +import type { Option } from 'payload' + +import { SelectField, useField } from '@payloadcms/ui' +import React from 'react' + +export const CustomSelect = ({ path }: { path: string }) => { + const { setValue, value } = useField({ path }) + const [options, setOptions] = React.useState<{ label: string; value: string }[]>([]) + + React.useEffect(() => { + const fetchOptions = () => { + const fetchedOptions = [ + { + label: 'Label 1', + value: 'value1', + }, + { + label: 'Label 2', + value: 'value2', + }, + ] + setOptions(fetchedOptions) + } + void fetchOptions() + }, []) + + const onChange = (selected: Option | Option[]) => { + const options = Array.isArray(selected) ? selected : [selected] + setValue(options.map((option) => (typeof option === 'string' ? option : option.value))) + } + + return ( +
+ +
+ ) +} diff --git a/test/admin/collections/CustomFields/index.ts b/test/admin/collections/CustomFields/index.ts new file mode 100644 index 0000000000..de155bef32 --- /dev/null +++ b/test/admin/collections/CustomFields/index.ts @@ -0,0 +1,19 @@ +import type { CollectionConfig } from 'payload' + +import { customFieldsSlug } from '../../slugs.js' +import { CustomSelect } from './components/CustomSelect.js' + +export const CustomFields: CollectionConfig = { + slug: customFieldsSlug, + fields: [ + { + name: 'customSelectField', + type: 'text', + admin: { + components: { + Field: CustomSelect, + }, + }, + }, + ], +} diff --git a/test/admin/config.ts b/test/admin/config.ts index d0dc608b5c..7025a2027f 100644 --- a/test/admin/config.ts +++ b/test/admin/config.ts @@ -3,6 +3,7 @@ import path from 'path' const filename = fileURLToPath(import.meta.url) const dirname = path.dirname(filename) import { buildConfigWithDefaults } from '../buildConfigWithDefaults.js' +import { CustomFields } from './collections/CustomFields/index.js' import { CustomIdRow } from './collections/CustomIdRow.js' import { CustomIdTab } from './collections/CustomIdTab.js' import { CustomViews1 } from './collections/CustomViews1.js' @@ -116,6 +117,7 @@ export default buildConfigWithDefaults({ CollectionNoApiView, CustomViews1, CustomViews2, + CustomFields, CollectionGroup1A, CollectionGroup1B, CollectionGroup2A, diff --git a/test/admin/e2e/1/e2e.spec.ts b/test/admin/e2e/1/e2e.spec.ts index 84a56bf587..bb5d8dc104 100644 --- a/test/admin/e2e/1/e2e.spec.ts +++ b/test/admin/e2e/1/e2e.spec.ts @@ -34,6 +34,7 @@ import { slugPluralLabel, } from '../../shared.js' import { + customFieldsSlug, customIdCollectionId, customViews2CollectionSlug, disableDuplicateSlug, @@ -70,6 +71,7 @@ describe('admin1', () => { let postsUrl: AdminUrlUtil let globalURL: AdminUrlUtil let customViewsURL: AdminUrlUtil + let customFieldsURL: AdminUrlUtil let disableDuplicateURL: AdminUrlUtil let serverURL: string let adminRoutes: ReturnType @@ -89,6 +91,7 @@ describe('admin1', () => { postsUrl = new AdminUrlUtil(serverURL, postsCollectionSlug) globalURL = new AdminUrlUtil(serverURL, globalSlug) customViewsURL = new AdminUrlUtil(serverURL, customViews2CollectionSlug) + customFieldsURL = new AdminUrlUtil(serverURL, customFieldsSlug) disableDuplicateURL = new AdminUrlUtil(serverURL, disableDuplicateSlug) const context = await browser.newContext() @@ -481,6 +484,16 @@ describe('admin1', () => { }) }) + describe('custom fields', () => { + describe('select field', () => { + test('should render custom select options', async () => { + await page.goto(customFieldsURL.create) + await page.locator('#field-customSelectField .rs__control').click() + await expect(page.locator('#field-customSelectField .rs__option')).toHaveCount(2) + }) + }) + }) + describe('API view', () => { test('collection — should not show API tab when disabled in config', async () => { await page.goto(postsUrl.collection(noApiViewCollectionSlug)) diff --git a/test/admin/slugs.ts b/test/admin/slugs.ts index ad51f9b711..38899b97c5 100644 --- a/test/admin/slugs.ts +++ b/test/admin/slugs.ts @@ -11,6 +11,7 @@ export const hiddenCollectionSlug = 'hidden-collection' export const noApiViewCollectionSlug = 'collection-no-api-view' export const disableDuplicateSlug = 'disable-duplicate' export const uploadCollectionSlug = 'uploads' +export const customFieldsSlug = 'custom-fields' export const collectionSlugs = [ usersCollectionSlug, customViews1CollectionSlug, @@ -23,6 +24,7 @@ export const collectionSlugs = [ group2Collection2Slug, hiddenCollectionSlug, noApiViewCollectionSlug, + customFieldsSlug, disableDuplicateSlug, ]