Skip to content

Commit 8773e3a

Browse files
fix: update select options when the options prop changes (#6878)
Fixes #6869 Before, options from props were being stored in state and would not update when props changed. Now options are memoized and will update when the incoming `options` prop changes.
1 parent 6ba619f commit 8773e3a

File tree

6 files changed

+83
-1
lines changed

6 files changed

+83
-1
lines changed

packages/ui/src/fields/Select/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ const SelectField: React.FC<SelectFieldProps> = (props) => {
6363
width,
6464
} = props
6565

66-
const [options] = useState(formatOptions(optionsFromProps))
66+
const options = React.useMemo(() => formatOptions(optionsFromProps), [optionsFromProps])
6767

6868
const memoizedValidate: ClientValidate = useCallback(
6969
(value, validationOptions) => {
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
'use client'
2+
3+
import type { Option } from 'payload'
4+
5+
import { SelectField, useField } from '@payloadcms/ui'
6+
import React from 'react'
7+
8+
export const CustomSelect = ({ path }: { path: string }) => {
9+
const { setValue, value } = useField<string>({ path })
10+
const [options, setOptions] = React.useState<{ label: string; value: string }[]>([])
11+
12+
React.useEffect(() => {
13+
const fetchOptions = () => {
14+
const fetchedOptions = [
15+
{
16+
label: 'Label 1',
17+
value: 'value1',
18+
},
19+
{
20+
label: 'Label 2',
21+
value: 'value2',
22+
},
23+
]
24+
setOptions(fetchedOptions)
25+
}
26+
void fetchOptions()
27+
}, [])
28+
29+
const onChange = (selected: Option | Option[]) => {
30+
const options = Array.isArray(selected) ? selected : [selected]
31+
setValue(options.map((option) => (typeof option === 'string' ? option : option.value)))
32+
}
33+
34+
return (
35+
<div>
36+
<SelectField
37+
hasMany
38+
name={path}
39+
onChange={onChange}
40+
options={options}
41+
path={path}
42+
value={value}
43+
/>
44+
</div>
45+
)
46+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import type { CollectionConfig } from 'payload'
2+
3+
import { customFieldsSlug } from '../../slugs.js'
4+
import { CustomSelect } from './components/CustomSelect.js'
5+
6+
export const CustomFields: CollectionConfig = {
7+
slug: customFieldsSlug,
8+
fields: [
9+
{
10+
name: 'customSelectField',
11+
type: 'text',
12+
admin: {
13+
components: {
14+
Field: CustomSelect,
15+
},
16+
},
17+
},
18+
],
19+
}

test/admin/config.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import path from 'path'
33
const filename = fileURLToPath(import.meta.url)
44
const dirname = path.dirname(filename)
55
import { buildConfigWithDefaults } from '../buildConfigWithDefaults.js'
6+
import { CustomFields } from './collections/CustomFields/index.js'
67
import { CustomIdRow } from './collections/CustomIdRow.js'
78
import { CustomIdTab } from './collections/CustomIdTab.js'
89
import { CustomViews1 } from './collections/CustomViews1.js'
@@ -116,6 +117,7 @@ export default buildConfigWithDefaults({
116117
CollectionNoApiView,
117118
CustomViews1,
118119
CustomViews2,
120+
CustomFields,
119121
CollectionGroup1A,
120122
CollectionGroup1B,
121123
CollectionGroup2A,

test/admin/e2e/1/e2e.spec.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import {
3434
slugPluralLabel,
3535
} from '../../shared.js'
3636
import {
37+
customFieldsSlug,
3738
customIdCollectionId,
3839
customViews2CollectionSlug,
3940
disableDuplicateSlug,
@@ -70,6 +71,7 @@ describe('admin1', () => {
7071
let postsUrl: AdminUrlUtil
7172
let globalURL: AdminUrlUtil
7273
let customViewsURL: AdminUrlUtil
74+
let customFieldsURL: AdminUrlUtil
7375
let disableDuplicateURL: AdminUrlUtil
7476
let serverURL: string
7577
let adminRoutes: ReturnType<typeof getAdminRoutes>
@@ -89,6 +91,7 @@ describe('admin1', () => {
8991
postsUrl = new AdminUrlUtil(serverURL, postsCollectionSlug)
9092
globalURL = new AdminUrlUtil(serverURL, globalSlug)
9193
customViewsURL = new AdminUrlUtil(serverURL, customViews2CollectionSlug)
94+
customFieldsURL = new AdminUrlUtil(serverURL, customFieldsSlug)
9295
disableDuplicateURL = new AdminUrlUtil(serverURL, disableDuplicateSlug)
9396

9497
const context = await browser.newContext()
@@ -481,6 +484,16 @@ describe('admin1', () => {
481484
})
482485
})
483486

487+
describe('custom fields', () => {
488+
describe('select field', () => {
489+
test('should render custom select options', async () => {
490+
await page.goto(customFieldsURL.create)
491+
await page.locator('#field-customSelectField .rs__control').click()
492+
await expect(page.locator('#field-customSelectField .rs__option')).toHaveCount(2)
493+
})
494+
})
495+
})
496+
484497
describe('API view', () => {
485498
test('collection — should not show API tab when disabled in config', async () => {
486499
await page.goto(postsUrl.collection(noApiViewCollectionSlug))

test/admin/slugs.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ export const hiddenCollectionSlug = 'hidden-collection'
1111
export const noApiViewCollectionSlug = 'collection-no-api-view'
1212
export const disableDuplicateSlug = 'disable-duplicate'
1313
export const uploadCollectionSlug = 'uploads'
14+
export const customFieldsSlug = 'custom-fields'
1415
export const collectionSlugs = [
1516
usersCollectionSlug,
1617
customViews1CollectionSlug,
@@ -23,6 +24,7 @@ export const collectionSlugs = [
2324
group2Collection2Slug,
2425
hiddenCollectionSlug,
2526
noApiViewCollectionSlug,
27+
customFieldsSlug,
2628
disableDuplicateSlug,
2729
]
2830

0 commit comments

Comments
 (0)