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>
This commit is contained in:
@@ -25,7 +25,7 @@ type SelectFieldBaseClientProps = {
|
||||
readonly onChange?: (e: string | string[]) => void
|
||||
readonly path: string
|
||||
readonly validate?: SelectFieldValidation
|
||||
readonly value?: string
|
||||
readonly value?: string | string[]
|
||||
}
|
||||
|
||||
type SelectFieldBaseServerProps = Pick<FieldPaths, 'path'>
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
'use client'
|
||||
|
||||
import type { Option, SelectFieldClientComponent } from 'payload'
|
||||
|
||||
import { SelectField, useField } from '@payloadcms/ui'
|
||||
import React from 'react'
|
||||
|
||||
export const CustomMultiSelect: SelectFieldClientComponent = (props) => {
|
||||
const { path } = props
|
||||
const { setValue, value } = useField<string[]>({ path })
|
||||
const [options, setOptions] = React.useState<Option[]>([])
|
||||
|
||||
React.useEffect(() => {
|
||||
const fetchOptions = () => {
|
||||
const fetched: Option[] = [
|
||||
{ label: 'Label 1', value: 'value1' },
|
||||
{ label: 'Label 2', value: 'value2' },
|
||||
]
|
||||
setOptions(fetched)
|
||||
}
|
||||
void fetchOptions()
|
||||
}, [])
|
||||
|
||||
const onChange = (val: string | string[]) => {
|
||||
setValue(Array.isArray(val) ? val : val ? [val] : [])
|
||||
}
|
||||
|
||||
return (
|
||||
<SelectField
|
||||
{...props}
|
||||
field={{
|
||||
...props.field,
|
||||
name: path,
|
||||
hasMany: true,
|
||||
options,
|
||||
}}
|
||||
onChange={onChange}
|
||||
value={value ?? []}
|
||||
/>
|
||||
)
|
||||
}
|
||||
@@ -8,28 +8,21 @@ import React from 'react'
|
||||
export const CustomSelect: SelectFieldClientComponent = (props) => {
|
||||
const { path } = props
|
||||
const { setValue, value } = useField<string>({ path })
|
||||
const [options, setOptions] = React.useState<{ label: string; value: string }[]>([])
|
||||
const [options, setOptions] = React.useState<Option[]>([])
|
||||
|
||||
React.useEffect(() => {
|
||||
const fetchOptions = () => {
|
||||
const fetchedOptions = [
|
||||
{
|
||||
label: 'Label 1',
|
||||
value: 'value1',
|
||||
},
|
||||
{
|
||||
label: 'Label 2',
|
||||
value: 'value2',
|
||||
},
|
||||
const fetchedOptions: Option[] = [
|
||||
{ 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)))
|
||||
const onChange = (val: string | string[]) => {
|
||||
setValue(Array.isArray(val) ? (val[0] ?? '') : val)
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -38,12 +31,10 @@ export const CustomSelect: SelectFieldClientComponent = (props) => {
|
||||
{...props}
|
||||
field={{
|
||||
...props.field,
|
||||
name: path,
|
||||
hasMany: true,
|
||||
options,
|
||||
}}
|
||||
onChange={onChange}
|
||||
value={value}
|
||||
value={value ?? ''}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -82,6 +82,16 @@ export const CustomFields: CollectionConfig = {
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'customMultiSelectField',
|
||||
type: 'text',
|
||||
hasMany: true,
|
||||
admin: {
|
||||
components: {
|
||||
Field: '/collections/CustomFields/fields/Select/CustomMultiSelect.js#CustomMultiSelect',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'relationshipFieldWithBeforeAfterInputs',
|
||||
type: 'relationship',
|
||||
|
||||
@@ -649,6 +649,26 @@ describe('Document View', () => {
|
||||
await page.locator('#field-customSelectField .rs__control').click()
|
||||
await expect(page.locator('#field-customSelectField .rs__option')).toHaveCount(2)
|
||||
})
|
||||
|
||||
test('should render custom multi select options', async () => {
|
||||
await page.goto(customFieldsURL.create)
|
||||
await page.locator('#field-customMultiSelectField .rs__control').click()
|
||||
await expect(page.locator('#field-customMultiSelectField .rs__option')).toHaveCount(2)
|
||||
})
|
||||
|
||||
test('should allow selecting multiple values in custom multi select', async () => {
|
||||
await page.goto(customFieldsURL.create)
|
||||
|
||||
const control = page.locator('#field-customMultiSelectField .rs__control')
|
||||
|
||||
await control.click()
|
||||
await page.locator('.rs__option', { hasText: 'Label 1' }).click()
|
||||
await expect(page.locator('#field-customMultiSelectField .rs__multi-value')).toHaveCount(1)
|
||||
|
||||
await control.click()
|
||||
await page.locator('.rs__option', { hasText: 'Label 2' }).click()
|
||||
await expect(page.locator('#field-customMultiSelectField .rs__multi-value')).toHaveCount(2)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -384,6 +384,7 @@ export interface CustomField {
|
||||
descriptionAsComponent?: string | null;
|
||||
customSelectField?: string | null;
|
||||
customSelectInput?: string | null;
|
||||
customMultiSelectField?: string[] | null;
|
||||
relationshipFieldWithBeforeAfterInputs?: (string | null) | Post;
|
||||
arrayFieldWithBeforeAfterInputs?:
|
||||
| {
|
||||
@@ -927,6 +928,7 @@ export interface CustomFieldsSelect<T extends boolean = true> {
|
||||
descriptionAsComponent?: T;
|
||||
customSelectField?: T;
|
||||
customSelectInput?: T;
|
||||
customMultiSelectField?: T;
|
||||
relationshipFieldWithBeforeAfterInputs?: T;
|
||||
arrayFieldWithBeforeAfterInputs?:
|
||||
| T
|
||||
|
||||
Reference in New Issue
Block a user