fix(ui): allow selectinputs to reset to their initial values if theres no provided value (#11252)
When reusing the SelectInput component from the UI package, if you set value to `''` it will continue to display the previously selected value instead of clearing out the field as expected. The ReactSelect component doesn't behave in this way and instead will clear out the field. This fix addresses this difference by resetting `valueToRender` inside the SelectInput to null.
This commit is contained in:
@@ -83,6 +83,9 @@ export const SelectInput: React.FC<SelectInputProps> = (props) => {
|
|||||||
label: matchingOption ? getTranslation(matchingOption.label, i18n) : value,
|
label: matchingOption ? getTranslation(matchingOption.label, i18n) : value,
|
||||||
value: matchingOption?.value ?? value,
|
value: matchingOption?.value ?? value,
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
// If value is not present then render nothing, allowing select fields to reset to their initial 'Select an option' state
|
||||||
|
valueToRender = null
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -0,0 +1,61 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import type { OptionObject, UIField } from 'payload'
|
||||||
|
|
||||||
|
import { SelectInput, useField } from '@payloadcms/ui'
|
||||||
|
import { useEffect, useMemo } from 'react'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
field: UIField
|
||||||
|
path: string
|
||||||
|
required?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
const selectOptions = [
|
||||||
|
{
|
||||||
|
label: 'Option 1',
|
||||||
|
value: 'option-1',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Option 2',
|
||||||
|
value: 'option-2',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
export function CustomInput({ field, path, required = false }: Props) {
|
||||||
|
const { setValue, value } = useField<string>({ path })
|
||||||
|
|
||||||
|
const options = useMemo(() => {
|
||||||
|
const internal: OptionObject[] = []
|
||||||
|
|
||||||
|
internal.push(...selectOptions)
|
||||||
|
|
||||||
|
return internal
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="custom-select-input">
|
||||||
|
<SelectInput
|
||||||
|
label={field.label}
|
||||||
|
name={field.name}
|
||||||
|
onChange={(option) => {
|
||||||
|
const selectedValue = (Array.isArray(option) ? option[0]?.value : option?.value) || ''
|
||||||
|
setValue(selectedValue)
|
||||||
|
}}
|
||||||
|
options={options}
|
||||||
|
path={path}
|
||||||
|
required={required}
|
||||||
|
value={value}
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
className="clear-value"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.preventDefault()
|
||||||
|
setValue('')
|
||||||
|
}}
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
Click me to reset value
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -73,6 +73,15 @@ export const CustomFields: CollectionConfig = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'customSelectInput',
|
||||||
|
type: 'text',
|
||||||
|
admin: {
|
||||||
|
components: {
|
||||||
|
Field: '/collections/CustomFields/fields/Select/CustomInput.js#CustomInput',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'relationshipFieldWithBeforeAfterInputs',
|
name: 'relationshipFieldWithBeforeAfterInputs',
|
||||||
type: 'relationship',
|
type: 'relationship',
|
||||||
|
|||||||
@@ -21,10 +21,10 @@ import {
|
|||||||
customEditLabel,
|
customEditLabel,
|
||||||
customNestedTabViewPath,
|
customNestedTabViewPath,
|
||||||
customNestedTabViewTitle,
|
customNestedTabViewTitle,
|
||||||
|
customTabAdminDescription,
|
||||||
customTabLabel,
|
customTabLabel,
|
||||||
customTabViewPath,
|
customTabViewPath,
|
||||||
customTabViewTitle,
|
customTabViewTitle,
|
||||||
customTabAdminDescription,
|
|
||||||
} from '../../shared.js'
|
} from '../../shared.js'
|
||||||
import {
|
import {
|
||||||
customFieldsSlug,
|
customFieldsSlug,
|
||||||
@@ -274,9 +274,7 @@ describe('Document View', () => {
|
|||||||
test('List drawer should not effect underlying breadcrumbs', async () => {
|
test('List drawer should not effect underlying breadcrumbs', async () => {
|
||||||
await navigateToDoc(page, postsUrl)
|
await navigateToDoc(page, postsUrl)
|
||||||
|
|
||||||
expect(await page.locator('.step-nav.app-header__step-nav a').nth(1).innerText()).toBe(
|
await expect(page.locator('.step-nav.app-header__step-nav a').nth(1)).toHaveText('Posts')
|
||||||
'Posts',
|
|
||||||
)
|
|
||||||
|
|
||||||
await page.locator('#field-upload button.upload__listToggler').click()
|
await page.locator('#field-upload button.upload__listToggler').click()
|
||||||
await expect(page.locator('[id^=list-drawer_1_]')).toBeVisible()
|
await expect(page.locator('[id^=list-drawer_1_]')).toBeVisible()
|
||||||
@@ -286,9 +284,7 @@ describe('Document View', () => {
|
|||||||
page.locator('.step-nav.app-header__step-nav .step-nav__last'),
|
page.locator('.step-nav.app-header__step-nav .step-nav__last'),
|
||||||
).not.toContainText('Uploads')
|
).not.toContainText('Uploads')
|
||||||
|
|
||||||
expect(await page.locator('.step-nav.app-header__step-nav a').nth(1).innerText()).toBe(
|
await expect(page.locator('.step-nav.app-header__step-nav a').nth(1)).toHaveText('Posts')
|
||||||
'Posts',
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -400,9 +396,9 @@ describe('Document View', () => {
|
|||||||
await page.waitForURL(postsUrl.create)
|
await page.waitForURL(postsUrl.create)
|
||||||
|
|
||||||
const secondTab = page.locator('.tabs-field__tab-button').nth(1)
|
const secondTab = page.locator('.tabs-field__tab-button').nth(1)
|
||||||
secondTab.click()
|
await secondTab.click()
|
||||||
|
|
||||||
wait(500)
|
await wait(500)
|
||||||
|
|
||||||
const tabsContent = page.locator('.tabs-field__content-wrap')
|
const tabsContent = page.locator('.tabs-field__content-wrap')
|
||||||
await expect(
|
await expect(
|
||||||
@@ -463,6 +459,24 @@ describe('Document View', () => {
|
|||||||
).toBeVisible()
|
).toBeVisible()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('custom select input can have its value cleared', async () => {
|
||||||
|
await page.goto(customFieldsURL.create)
|
||||||
|
await page.waitForURL(customFieldsURL.create)
|
||||||
|
await expect(page.locator('#field-customSelectInput')).toBeVisible()
|
||||||
|
|
||||||
|
await page.locator('#field-customSelectInput .rs__control').click()
|
||||||
|
await page.locator('#field-customSelectInput .rs__option').first().click()
|
||||||
|
|
||||||
|
await expect(page.locator('#field-customSelectInput .rs__single-value')).toHaveText(
|
||||||
|
'Option 1',
|
||||||
|
)
|
||||||
|
|
||||||
|
await page.locator('.clear-value').click()
|
||||||
|
await expect(page.locator('#field-customSelectInput .rs__placeholder')).toHaveText(
|
||||||
|
'Select a value',
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
describe('field descriptions', () => {
|
describe('field descriptions', () => {
|
||||||
test('should render static field description', async () => {
|
test('should render static field description', async () => {
|
||||||
await page.goto(customFieldsURL.create)
|
await page.goto(customFieldsURL.create)
|
||||||
@@ -535,7 +549,7 @@ describe('Document View', () => {
|
|||||||
describe('publish button', () => {
|
describe('publish button', () => {
|
||||||
test('should show publish active locale button with defaultLocalePublishOption', async () => {
|
test('should show publish active locale button with defaultLocalePublishOption', async () => {
|
||||||
await navigateToDoc(page, postsUrl)
|
await navigateToDoc(page, postsUrl)
|
||||||
const publishButton = await page.locator('#action-save')
|
const publishButton = page.locator('#action-save')
|
||||||
await expect(publishButton).toBeVisible()
|
await expect(publishButton).toBeVisible()
|
||||||
await expect(publishButton).toContainText('Publish in English')
|
await expect(publishButton).toContainText('Publish in English')
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -334,6 +334,7 @@ export interface CustomField {
|
|||||||
descriptionAsFunction?: string | null;
|
descriptionAsFunction?: string | null;
|
||||||
descriptionAsComponent?: string | null;
|
descriptionAsComponent?: string | null;
|
||||||
customSelectField?: string | null;
|
customSelectField?: string | null;
|
||||||
|
customSelectInput?: string | null;
|
||||||
relationshipFieldWithBeforeAfterInputs?: (string | null) | Post;
|
relationshipFieldWithBeforeAfterInputs?: (string | null) | Post;
|
||||||
arrayFieldWithBeforeAfterInputs?:
|
arrayFieldWithBeforeAfterInputs?:
|
||||||
| {
|
| {
|
||||||
@@ -717,6 +718,7 @@ export interface CustomFieldsSelect<T extends boolean = true> {
|
|||||||
descriptionAsFunction?: T;
|
descriptionAsFunction?: T;
|
||||||
descriptionAsComponent?: T;
|
descriptionAsComponent?: T;
|
||||||
customSelectField?: T;
|
customSelectField?: T;
|
||||||
|
customSelectInput?: T;
|
||||||
relationshipFieldWithBeforeAfterInputs?: T;
|
relationshipFieldWithBeforeAfterInputs?: T;
|
||||||
arrayFieldWithBeforeAfterInputs?:
|
arrayFieldWithBeforeAfterInputs?:
|
||||||
| T
|
| T
|
||||||
|
|||||||
Reference in New Issue
Block a user