fix: SelectItem, ListboxItem, and AutocompleteItem not to accept value props (#4653)

* fix(select): `SelectItem` does not accept value props

* refactor: do not use the index as `key`

* Update .changeset/light-hairs-draw.md

* chore: remove unnecessary `value` props

* chore: update changeset

* refactor: remove unnecessary value prop

---------

Co-authored-by: WK Wong <wingkwong.code@gmail.com>
This commit is contained in:
Ryo Matsukawa 2025-02-11 22:07:20 +09:00 committed by GitHub
parent 8af2c5d8b1
commit 28b8606411
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 93 additions and 229 deletions

View File

@ -0,0 +1,7 @@
---
"@heroui/autocomplete": patch
"@heroui/listbox": patch
"@heroui/select": patch
---
Fix SelectItem, ListboxItem, and AutocompleteItem not to accept value props (#2283)

View File

@ -116,21 +116,11 @@ export default function App() {
name="country" name="country"
placeholder="Select country" placeholder="Select country"
> >
<SelectItem key="ar" value="ar"> <SelectItem key="ar">Argentina</SelectItem>
Argentina <SelectItem key="us">United States</SelectItem>
</SelectItem> <SelectItem key="ca">Canada</SelectItem>
<SelectItem key="us" value="us"> <SelectItem key="uk">United Kingdom</SelectItem>
United States <SelectItem key="au">Australia</SelectItem>
</SelectItem>
<SelectItem key="ca" value="ca">
Canada
</SelectItem>
<SelectItem key="uk" value="uk">
United Kingdom
</SelectItem>
<SelectItem key="au" value="au">
Australia
</SelectItem>
</Select> </Select>
<Checkbox <Checkbox

View File

@ -45,10 +45,8 @@ export default function App() {
maxListboxHeight={400} maxListboxHeight={400}
placeholder="Select..." placeholder="Select..."
> >
{items.map((item, index) => ( {items.map((item) => (
<SelectItem key={index} value={item.value}> <SelectItem key={item.value}>{item.label}</SelectItem>
{item.label}
</SelectItem>
))} ))}
</Select> </Select>
</div> </div>

View File

@ -44,10 +44,8 @@ export default function App() {
maxListboxHeight={400} maxListboxHeight={400}
placeholder="Select..." placeholder="Select..."
> >
{items.map((item, index) => ( {items.map((item) => (
<SelectItem key={index} value={item.value}> <SelectItem key={item.value}>{item.label}</SelectItem>
{item.label}
</SelectItem>
))} ))}
</Select> </Select>
</div> </div>

View File

@ -44,9 +44,7 @@ export default function App() {
placeholder="Select..." placeholder="Select..."
> >
{items.map((item, index) => ( {items.map((item, index) => (
<SelectItem key={index} value={item.value}> <SelectItem key={index}>{item.label}</SelectItem>
{item.label}
</SelectItem>
))} ))}
</Select> </Select>
</div> </div>

View File

@ -43,10 +43,8 @@ export default function App() {
label={"Select from 1000 items"} label={"Select from 1000 items"}
placeholder="Select..." placeholder="Select..."
> >
{items.map((item, index) => ( {items.map((item) => (
<SelectItem key={index} value={item.value}> <SelectItem key={item.value}>{item.label}</SelectItem>
{item.label}
</SelectItem>
))} ))}
</Select> </Select>
</div> </div>

View File

@ -295,6 +295,13 @@ the popover and listbox components.
<CodeDemo title="Custom Styles" files={selectContent.customStyles} /> <CodeDemo title="Custom Styles" files={selectContent.customStyles} />
### Using `value` attribute in option
The [`value`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/option#value) attribute is not directly supported in `SelectItem`. Instead, the `key` property should be used to set the `value` to be submitted in forms.
If you need to submit a specific `value` instead of the `key` during form submission, consider implementing a lookup map in your application.
## Slots ## Slots
- **base**: The main wrapper of the select. This wraps the rest of the slots. - **base**: The main wrapper of the select. This wraps the rest of the slots.

View File

@ -67,15 +67,9 @@ const ControlledAutocomplete = <T extends object>(props: AutocompleteProps<T>) =
const AutocompleteExample = (props: Partial<AutocompleteProps> = {}) => ( const AutocompleteExample = (props: Partial<AutocompleteProps> = {}) => (
<Autocomplete label="Favorite Animal" {...props}> <Autocomplete label="Favorite Animal" {...props}>
<AutocompleteItem key="penguin" value="penguin"> <AutocompleteItem key="penguin">Penguin</AutocompleteItem>
Penguin <AutocompleteItem key="zebra">Zebra</AutocompleteItem>
</AutocompleteItem> <AutocompleteItem key="shark">Shark</AutocompleteItem>
<AutocompleteItem key="zebra" value="zebra">
Zebra
</AutocompleteItem>
<AutocompleteItem key="shark" value="shark">
Shark
</AutocompleteItem>
</Autocomplete> </Autocomplete>
); );
@ -102,15 +96,9 @@ describe("Autocomplete", () => {
render( render(
<Autocomplete ref={ref} aria-label="Favorite Animal" label="Favorite Animal"> <Autocomplete ref={ref} aria-label="Favorite Animal" label="Favorite Animal">
<AutocompleteItem key="penguin" value="penguin"> <AutocompleteItem key="penguin">Penguin</AutocompleteItem>
Penguin <AutocompleteItem key="zebra">Zebra</AutocompleteItem>
</AutocompleteItem> <AutocompleteItem key="shark">Shark</AutocompleteItem>
<AutocompleteItem key="zebra" value="zebra">
Zebra
</AutocompleteItem>
<AutocompleteItem key="shark" value="shark">
Shark
</AutocompleteItem>
</Autocomplete>, </Autocomplete>,
); );
@ -131,17 +119,11 @@ describe("Autocomplete", () => {
const wrapper = render( const wrapper = render(
<Autocomplete aria-label="Favorite Animal" label="Favorite Animal"> <Autocomplete aria-label="Favorite Animal" label="Favorite Animal">
<AutocompleteSection title="Birds"> <AutocompleteSection title="Birds">
<AutocompleteItem key="penguin" value="penguin"> <AutocompleteItem key="penguin">Penguin</AutocompleteItem>
Penguin
</AutocompleteItem>
</AutocompleteSection> </AutocompleteSection>
<AutocompleteSection title="Mammals"> <AutocompleteSection title="Mammals">
<AutocompleteItem key="zebra" value="zebra"> <AutocompleteItem key="zebra">Zebra</AutocompleteItem>
Zebra <AutocompleteItem key="shark">Shark</AutocompleteItem>
</AutocompleteItem>
<AutocompleteItem key="shark" value="shark">
Shark
</AutocompleteItem>
</AutocompleteSection> </AutocompleteSection>
</Autocomplete>, </Autocomplete>,
); );
@ -171,15 +153,9 @@ describe("Autocomplete", () => {
it("should focus when clicking autocomplete", async () => { it("should focus when clicking autocomplete", async () => {
const wrapper = render( const wrapper = render(
<Autocomplete aria-label="Favorite Animal" data-testid="autocomplete" label="Favorite Animal"> <Autocomplete aria-label="Favorite Animal" data-testid="autocomplete" label="Favorite Animal">
<AutocompleteItem key="penguin" value="penguin"> <AutocompleteItem key="penguin">Penguin</AutocompleteItem>
Penguin <AutocompleteItem key="zebra">Zebra</AutocompleteItem>
</AutocompleteItem> <AutocompleteItem key="shark">Shark</AutocompleteItem>
<AutocompleteItem key="zebra" value="zebra">
Zebra
</AutocompleteItem>
<AutocompleteItem key="shark" value="shark">
Shark
</AutocompleteItem>
</Autocomplete>, </Autocomplete>,
); );
@ -198,15 +174,9 @@ describe("Autocomplete", () => {
it("should clear value after clicking clear button", async () => { it("should clear value after clicking clear button", async () => {
const wrapper = render( const wrapper = render(
<Autocomplete aria-label="Favorite Animal" data-testid="autocomplete" label="Favorite Animal"> <Autocomplete aria-label="Favorite Animal" data-testid="autocomplete" label="Favorite Animal">
<AutocompleteItem key="penguin" value="penguin"> <AutocompleteItem key="penguin">Penguin</AutocompleteItem>
Penguin <AutocompleteItem key="zebra">Zebra</AutocompleteItem>
</AutocompleteItem> <AutocompleteItem key="shark">Shark</AutocompleteItem>
<AutocompleteItem key="zebra" value="zebra">
Zebra
</AutocompleteItem>
<AutocompleteItem key="shark" value="shark">
Shark
</AutocompleteItem>
</Autocomplete>, </Autocomplete>,
); );
@ -244,15 +214,9 @@ describe("Autocomplete", () => {
it("should clear arbitrary value after clicking clear button", async () => { it("should clear arbitrary value after clicking clear button", async () => {
const wrapper = render( const wrapper = render(
<Autocomplete aria-label="Favorite Animal" data-testid="autocomplete" label="Favorite Animal"> <Autocomplete aria-label="Favorite Animal" data-testid="autocomplete" label="Favorite Animal">
<AutocompleteItem key="penguin" value="penguin"> <AutocompleteItem key="penguin">Penguin</AutocompleteItem>
Penguin <AutocompleteItem key="zebra">Zebra</AutocompleteItem>
</AutocompleteItem> <AutocompleteItem key="shark">Shark</AutocompleteItem>
<AutocompleteItem key="zebra" value="zebra">
Zebra
</AutocompleteItem>
<AutocompleteItem key="shark" value="shark">
Shark
</AutocompleteItem>
</Autocomplete>, </Autocomplete>,
); );
@ -287,15 +251,9 @@ describe("Autocomplete", () => {
it("should keep the ListBox open after clicking clear button", async () => { it("should keep the ListBox open after clicking clear button", async () => {
const wrapper = render( const wrapper = render(
<Autocomplete aria-label="Favorite Animal" data-testid="autocomplete" label="Favorite Animal"> <Autocomplete aria-label="Favorite Animal" data-testid="autocomplete" label="Favorite Animal">
<AutocompleteItem key="penguin" value="penguin"> <AutocompleteItem key="penguin">Penguin</AutocompleteItem>
Penguin <AutocompleteItem key="zebra">Zebra</AutocompleteItem>
</AutocompleteItem> <AutocompleteItem key="shark">Shark</AutocompleteItem>
<AutocompleteItem key="zebra" value="zebra">
Zebra
</AutocompleteItem>
<AutocompleteItem key="shark" value="shark">
Shark
</AutocompleteItem>
</Autocomplete>, </Autocomplete>,
); );
@ -403,15 +361,9 @@ describe("Autocomplete", () => {
it("should open and close listbox by clicking selector button", async () => { it("should open and close listbox by clicking selector button", async () => {
const wrapper = render( const wrapper = render(
<Autocomplete aria-label="Favorite Animal" data-testid="autocomplete" label="Favorite Animal"> <Autocomplete aria-label="Favorite Animal" data-testid="autocomplete" label="Favorite Animal">
<AutocompleteItem key="penguin" value="penguin"> <AutocompleteItem key="penguin">Penguin</AutocompleteItem>
Penguin <AutocompleteItem key="zebra">Zebra</AutocompleteItem>
</AutocompleteItem> <AutocompleteItem key="shark">Shark</AutocompleteItem>
<AutocompleteItem key="zebra" value="zebra">
Zebra
</AutocompleteItem>
<AutocompleteItem key="shark" value="shark">
Shark
</AutocompleteItem>
</Autocomplete>, </Autocomplete>,
); );
@ -451,15 +403,9 @@ describe("Autocomplete", () => {
data-testid="close-when-clicking-outside-test" data-testid="close-when-clicking-outside-test"
label="Favorite Animal" label="Favorite Animal"
> >
<AutocompleteItem key="penguin" value="penguin"> <AutocompleteItem key="penguin">Penguin</AutocompleteItem>
Penguin <AutocompleteItem key="zebra">Zebra</AutocompleteItem>
</AutocompleteItem> <AutocompleteItem key="shark">Shark</AutocompleteItem>
<AutocompleteItem key="zebra" value="zebra">
Zebra
</AutocompleteItem>
<AutocompleteItem key="shark" value="shark">
Shark
</AutocompleteItem>
</Autocomplete>, </Autocomplete>,
); );
@ -492,15 +438,9 @@ describe("Autocomplete", () => {
data-testid="close-when-clicking-outside-test" data-testid="close-when-clicking-outside-test"
label="Favorite Animal" label="Favorite Animal"
> >
<AutocompleteItem key="penguin" value="penguin"> <AutocompleteItem key="penguin">Penguin</AutocompleteItem>
Penguin <AutocompleteItem key="zebra">Zebra</AutocompleteItem>
</AutocompleteItem> <AutocompleteItem key="shark">Shark</AutocompleteItem>
<AutocompleteItem key="zebra" value="zebra">
Zebra
</AutocompleteItem>
<AutocompleteItem key="shark" value="shark">
Shark
</AutocompleteItem>
</Autocomplete> </Autocomplete>
</ModalBody> </ModalBody>
<ModalFooter>Modal footer</ModalFooter> <ModalFooter>Modal footer</ModalFooter>
@ -527,15 +467,9 @@ describe("Autocomplete", () => {
it("should set the input after selection", async () => { it("should set the input after selection", async () => {
const wrapper = render( const wrapper = render(
<Autocomplete aria-label="Favorite Animal" data-testid="autocomplete" label="Favorite Animal"> <Autocomplete aria-label="Favorite Animal" data-testid="autocomplete" label="Favorite Animal">
<AutocompleteItem key="penguin" value="penguin"> <AutocompleteItem key="penguin">Penguin</AutocompleteItem>
Penguin <AutocompleteItem key="zebra">Zebra</AutocompleteItem>
</AutocompleteItem> <AutocompleteItem key="shark">Shark</AutocompleteItem>
<AutocompleteItem key="zebra" value="zebra">
Zebra
</AutocompleteItem>
<AutocompleteItem key="shark" value="shark">
Shark
</AutocompleteItem>
</Autocomplete>, </Autocomplete>,
); );
@ -569,30 +503,18 @@ describe("Autocomplete", () => {
data-testid="autocomplete" data-testid="autocomplete"
label="Favorite Animal" label="Favorite Animal"
> >
<AutocompleteItem key="penguin" value="penguin"> <AutocompleteItem key="penguin">Penguin</AutocompleteItem>
Penguin <AutocompleteItem key="zebra">Zebra</AutocompleteItem>
</AutocompleteItem> <AutocompleteItem key="shark">Shark</AutocompleteItem>
<AutocompleteItem key="zebra" value="zebra">
Zebra
</AutocompleteItem>
<AutocompleteItem key="shark" value="shark">
Shark
</AutocompleteItem>
</Autocomplete> </Autocomplete>
<Autocomplete <Autocomplete
aria-label="Favorite Animal" aria-label="Favorite Animal"
data-testid="autocomplete2" data-testid="autocomplete2"
label="Favorite Animal" label="Favorite Animal"
> >
<AutocompleteItem key="penguin" value="penguin"> <AutocompleteItem key="penguin">Penguin</AutocompleteItem>
Penguin <AutocompleteItem key="zebra">Zebra</AutocompleteItem>
</AutocompleteItem> <AutocompleteItem key="shark">Shark</AutocompleteItem>
<AutocompleteItem key="zebra" value="zebra">
Zebra
</AutocompleteItem>
<AutocompleteItem key="shark" value="shark">
Shark
</AutocompleteItem>
</Autocomplete> </Autocomplete>
</>, </>,
); );

View File

@ -89,9 +89,7 @@ const defaultProps = {
}; };
const items = animalsData.map((item) => ( const items = animalsData.map((item) => (
<AutocompleteItem key={item.value} value={item.value}> <AutocompleteItem key={item.value}>{item.label}</AutocompleteItem>
{item.label}
</AutocompleteItem>
)); ));
interface LargeDatasetSchema { interface LargeDatasetSchema {
@ -138,9 +136,7 @@ const LargeDatasetTemplate = (args: AutocompleteProps & {numItems: number}) => {
return ( return (
<Autocomplete label={`Search from ${args.numItems} items`} {...args}> <Autocomplete label={`Search from ${args.numItems} items`} {...args}>
{largeDataset.map((item, index) => ( {largeDataset.map((item, index) => (
<AutocompleteItem key={index} value={item.value}> <AutocompleteItem key={index}>{item.label}</AutocompleteItem>
{item.label}
</AutocompleteItem>
))} ))}
</Autocomplete> </Autocomplete>
); );

View File

@ -103,7 +103,7 @@ export type ListboxItemBaseProps<T extends object = {}> = Omit<Props<T>, "onClic
}; };
const ListboxItemBase = BaseItem as <T extends object>( const ListboxItemBase = BaseItem as <T extends object>(
props: ListboxItemBaseProps<T>, props: Omit<ListboxItemBaseProps<T>, "value">,
) => JSX.Element; ) => JSX.Element;
export default ListboxItemBase; export default ListboxItemBase;

View File

@ -725,9 +725,7 @@ const LargeDatasetTemplate = (args: ListboxProps & {numItems: number}) => {
<div className="flex w-full max-w-full py-20 px-20"> <div className="flex w-full max-w-full py-20 px-20">
<Listbox label={`Select from ${args.numItems} items`} {...args}> <Listbox label={`Select from ${args.numItems} items`} {...args}>
{largeDataset.map((item, index) => ( {largeDataset.map((item, index) => (
<ListboxItem key={index} value={item.value}> <ListboxItem key={index}>{item.label}</ListboxItem>
{item.label}
</ListboxItem>
))} ))}
</Listbox> </Listbox>
</div> </div>

View File

@ -526,15 +526,9 @@ describe("Select", () => {
data-testid="select" data-testid="select"
label="Favorite Animal" label="Favorite Animal"
> >
<SelectItem key="penguin" value="penguin"> <SelectItem key="penguin">Penguin</SelectItem>
Penguin <SelectItem key="zebra">Zebra</SelectItem>
</SelectItem> <SelectItem key="shark">Shark</SelectItem>
<SelectItem key="zebra" value="zebra">
Zebra
</SelectItem>
<SelectItem key="shark" value="shark">
Shark
</SelectItem>
</Select> </Select>
<Select <Select
disableAnimation disableAnimation
@ -542,15 +536,9 @@ describe("Select", () => {
data-testid="select2" data-testid="select2"
label="Favorite Animal" label="Favorite Animal"
> >
<SelectItem key="penguin" value="penguin"> <SelectItem key="penguin">Penguin</SelectItem>
Penguin <SelectItem key="zebra">Zebra</SelectItem>
</SelectItem> <SelectItem key="shark">Shark</SelectItem>
<SelectItem key="zebra" value="zebra">
Zebra
</SelectItem>
<SelectItem key="shark" value="shark">
Shark
</SelectItem>
</Select> </Select>
</>, </>,
); );
@ -688,15 +676,9 @@ describe("Select", () => {
data-testid="select" data-testid="select"
label="Favorite Animal" label="Favorite Animal"
> >
<SelectItem key="penguin" value="penguin"> <SelectItem key="penguin">Penguin</SelectItem>
Penguin <SelectItem key="zebra">Zebra</SelectItem>
</SelectItem> <SelectItem key="shark">Shark</SelectItem>
<SelectItem key="zebra" value="zebra">
Zebra
</SelectItem>
<SelectItem key="shark" value="shark">
Shark
</SelectItem>
</Select>, </Select>,
); );
@ -736,9 +718,7 @@ describe("Select", () => {
onChange={onChange} onChange={onChange}
> >
{options.map((o) => ( {options.map((o) => (
<SelectItem key={o} value={o}> <SelectItem key={o}>{o}</SelectItem>
{o}
</SelectItem>
))} ))}
</Select>, </Select>,
); );
@ -775,9 +755,7 @@ describe("Select", () => {
onChange={onChange} onChange={onChange}
> >
{options.map((o) => ( {options.map((o) => (
<SelectItem key={o} value={o}> <SelectItem key={o}>{o}</SelectItem>
{o}
</SelectItem>
))} ))}
</Select>, </Select>,
); );
@ -808,15 +786,9 @@ describe("Select", () => {
labelPlacement="outside" labelPlacement="outside"
placeholder="placeholder" placeholder="placeholder"
> >
<SelectItem key="penguin" value="penguin"> <SelectItem key="penguin">Penguin</SelectItem>
Penguin <SelectItem key="zebra">Zebra</SelectItem>
</SelectItem> <SelectItem key="shark">Shark</SelectItem>
<SelectItem key="zebra" value="zebra">
Zebra
</SelectItem>
<SelectItem key="shark" value="shark">
Shark
</SelectItem>
</Select>, </Select>,
); );
@ -838,15 +810,9 @@ describe("Select", () => {
label={labelContent} label={labelContent}
placeholder="placeholder" placeholder="placeholder"
> >
<SelectItem key="penguin" value="penguin"> <SelectItem key="penguin">Penguin</SelectItem>
Penguin <SelectItem key="zebra">Zebra</SelectItem>
</SelectItem> <SelectItem key="shark">Shark</SelectItem>
<SelectItem key="zebra" value="zebra">
Zebra
</SelectItem>
<SelectItem key="shark" value="shark">
Shark
</SelectItem>
</Select>, </Select>,
); );
@ -972,9 +938,7 @@ describe("Select virtualization tests", () => {
onChange={onChange} onChange={onChange}
> >
{options.map((o) => ( {options.map((o) => (
<SelectItem key={o} value={o}> <SelectItem key={o}>{o}</SelectItem>
{o}
</SelectItem>
))} ))}
</Select> </Select>
</div>, </div>,
@ -1007,9 +971,7 @@ describe("Select virtualization tests", () => {
onChange={onChange} onChange={onChange}
> >
{options.map((o) => ( {options.map((o) => (
<SelectItem key={o} value={o}> <SelectItem key={o}>{o}</SelectItem>
{o}
</SelectItem>
))} ))}
</Select> </Select>
</div>, </div>,
@ -1060,9 +1022,7 @@ describe("Select virtualization tests", () => {
onChange={onChange} onChange={onChange}
> >
{options.map((o) => ( {options.map((o) => (
<SelectItem key={o} value={o}> <SelectItem key={o}>{o}</SelectItem>
{o}
</SelectItem>
))} ))}
</Select> </Select>
</div>, </div>,
@ -1115,9 +1075,7 @@ describe("Select virtualization tests", () => {
onChange={onChange} onChange={onChange}
> >
{options.map((o) => ( {options.map((o) => (
<SelectItem key={o} value={o}> <SelectItem key={o}>{o}</SelectItem>
{o}
</SelectItem>
))} ))}
</Select> </Select>
</div>, </div>,

View File

@ -69,11 +69,7 @@ const defaultProps = {
...select.defaultVariants, ...select.defaultVariants,
}; };
const items = animalsData.map((item) => ( const items = animalsData.map((item) => <SelectItem key={item.value}>{item.label}</SelectItem>);
<SelectItem key={item.value} value={item.value}>
{item.label}
</SelectItem>
));
const Template = ({color, variant, ...args}: SelectProps) => ( const Template = ({color, variant, ...args}: SelectProps) => (
<Select className="max-w-xs" color={color} label="Favorite Animal" variant={variant} {...args}> <Select className="max-w-xs" color={color} label="Favorite Animal" variant={variant} {...args}>
@ -885,10 +881,8 @@ const LargeDatasetTemplate = (args: SelectProps & {numItems: number}) => {
return ( return (
<div className="flex w-full max-w-full py-20 xl:px-32 lg:px-20 px-20"> <div className="flex w-full max-w-full py-20 xl:px-32 lg:px-20 px-20">
<Select label={`Select from ${args.numItems} items`} {...args}> <Select label={`Select from ${args.numItems} items`} {...args}>
{largeDataset.map((item, index) => ( {largeDataset.map((item) => (
<SelectItem key={index} value={item.value}> <SelectItem key={item.value}>{item.label}</SelectItem>
{item.label}
</SelectItem>
))} ))}
</Select> </Select>
</div> </div>