Skip to content

Commit 4f6d0d8

Browse files
fix: select field component value prop type does not support array values (#13510)
### What? Update `SelectFieldBaseClientProps` type so `value` accepts `string[]` for `hasMany` selects ### Why? Multi-selects currently error with “Type 'string[]' is not assignable to type 'string'”. ### How? - Change `value?: string` to `value?: string | string[]` - Also adds additional multi select custom component to `admin` test suite for testing --------- Co-authored-by: Jarrod Flesch <jarrodmflesch@gmail.com>
1 parent 9e7bb24 commit 4f6d0d8

File tree

6 files changed

+81
-17
lines changed

6 files changed

+81
-17
lines changed

packages/payload/src/admin/fields/Select.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ type SelectFieldBaseClientProps = {
2525
readonly onChange?: (e: string | string[]) => void
2626
readonly path: string
2727
readonly validate?: SelectFieldValidation
28-
readonly value?: string
28+
readonly value?: string | string[]
2929
}
3030

3131
type SelectFieldBaseServerProps = Pick<FieldPaths, 'path'>
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
'use client'
2+
3+
import type { Option, SelectFieldClientComponent } from 'payload'
4+
5+
import { SelectField, useField } from '@payloadcms/ui'
6+
import React from 'react'
7+
8+
export const CustomMultiSelect: SelectFieldClientComponent = (props) => {
9+
const { path } = props
10+
const { setValue, value } = useField<string[]>({ path })
11+
const [options, setOptions] = React.useState<Option[]>([])
12+
13+
React.useEffect(() => {
14+
const fetchOptions = () => {
15+
const fetched: Option[] = [
16+
{ label: 'Label 1', value: 'value1' },
17+
{ label: 'Label 2', value: 'value2' },
18+
]
19+
setOptions(fetched)
20+
}
21+
void fetchOptions()
22+
}, [])
23+
24+
const onChange = (val: string | string[]) => {
25+
setValue(Array.isArray(val) ? val : val ? [val] : [])
26+
}
27+
28+
return (
29+
<SelectField
30+
{...props}
31+
field={{
32+
...props.field,
33+
name: path,
34+
hasMany: true,
35+
options,
36+
}}
37+
onChange={onChange}
38+
value={value ?? []}
39+
/>
40+
)
41+
}

test/admin/collections/CustomFields/fields/Select/index.tsx

Lines changed: 7 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -8,28 +8,21 @@ import React from 'react'
88
export const CustomSelect: SelectFieldClientComponent = (props) => {
99
const { path } = props
1010
const { setValue, value } = useField<string>({ path })
11-
const [options, setOptions] = React.useState<{ label: string; value: string }[]>([])
11+
const [options, setOptions] = React.useState<Option[]>([])
1212

1313
React.useEffect(() => {
1414
const fetchOptions = () => {
15-
const fetchedOptions = [
16-
{
17-
label: 'Label 1',
18-
value: 'value1',
19-
},
20-
{
21-
label: 'Label 2',
22-
value: 'value2',
23-
},
15+
const fetchedOptions: Option[] = [
16+
{ label: 'Label 1', value: 'value1' },
17+
{ label: 'Label 2', value: 'value2' },
2418
]
2519
setOptions(fetchedOptions)
2620
}
2721
void fetchOptions()
2822
}, [])
2923

30-
const onChange = (selected: Option | Option[]) => {
31-
const options = Array.isArray(selected) ? selected : [selected]
32-
setValue(options.map((option) => (typeof option === 'string' ? option : option.value)))
24+
const onChange = (val: string | string[]) => {
25+
setValue(Array.isArray(val) ? (val[0] ?? '') : val)
3326
}
3427

3528
return (
@@ -38,12 +31,10 @@ export const CustomSelect: SelectFieldClientComponent = (props) => {
3831
{...props}
3932
field={{
4033
...props.field,
41-
name: path,
42-
hasMany: true,
4334
options,
4435
}}
4536
onChange={onChange}
46-
value={value}
37+
value={value ?? ''}
4738
/>
4839
</div>
4940
)

test/admin/collections/CustomFields/index.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,16 @@ export const CustomFields: CollectionConfig = {
8282
},
8383
},
8484
},
85+
{
86+
name: 'customMultiSelectField',
87+
type: 'text',
88+
hasMany: true,
89+
admin: {
90+
components: {
91+
Field: '/collections/CustomFields/fields/Select/CustomMultiSelect.js#CustomMultiSelect',
92+
},
93+
},
94+
},
8595
{
8696
name: 'relationshipFieldWithBeforeAfterInputs',
8797
type: 'relationship',

test/admin/e2e/document-view/e2e.spec.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -649,6 +649,26 @@ describe('Document View', () => {
649649
await page.locator('#field-customSelectField .rs__control').click()
650650
await expect(page.locator('#field-customSelectField .rs__option')).toHaveCount(2)
651651
})
652+
653+
test('should render custom multi select options', async () => {
654+
await page.goto(customFieldsURL.create)
655+
await page.locator('#field-customMultiSelectField .rs__control').click()
656+
await expect(page.locator('#field-customMultiSelectField .rs__option')).toHaveCount(2)
657+
})
658+
659+
test('should allow selecting multiple values in custom multi select', async () => {
660+
await page.goto(customFieldsURL.create)
661+
662+
const control = page.locator('#field-customMultiSelectField .rs__control')
663+
664+
await control.click()
665+
await page.locator('.rs__option', { hasText: 'Label 1' }).click()
666+
await expect(page.locator('#field-customMultiSelectField .rs__multi-value')).toHaveCount(1)
667+
668+
await control.click()
669+
await page.locator('.rs__option', { hasText: 'Label 2' }).click()
670+
await expect(page.locator('#field-customMultiSelectField .rs__multi-value')).toHaveCount(2)
671+
})
652672
})
653673
})
654674

test/admin/payload-types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -384,6 +384,7 @@ export interface CustomField {
384384
descriptionAsComponent?: string | null;
385385
customSelectField?: string | null;
386386
customSelectInput?: string | null;
387+
customMultiSelectField?: string[] | null;
387388
relationshipFieldWithBeforeAfterInputs?: (string | null) | Post;
388389
arrayFieldWithBeforeAfterInputs?:
389390
| {
@@ -927,6 +928,7 @@ export interface CustomFieldsSelect<T extends boolean = true> {
927928
descriptionAsComponent?: T;
928929
customSelectField?: T;
929930
customSelectInput?: T;
931+
customMultiSelectField?: T;
930932
relationshipFieldWithBeforeAfterInputs?: T;
931933
arrayFieldWithBeforeAfterInputs?:
932934
| T

0 commit comments

Comments
 (0)