Provide a general summary of the issue here
The Picker component supports items with numeric id / key props.
Item.key unset
If the Item.key
prop is not explicitly set, keys get derived from the id
or key
props on the items array.
e.g.
function Example() {
let items = [
{ id: 1, name: 'Aardvark' },
{ id: 2, name: 'Cat' },
{ id: 3, name: 'Dog' }, ];
let [animalId, setAnimalId] = React.useState(null);
return (
<>
<Picker
label="Pick an animal"
items={items}
onSelectionChange={setAnimalId}
value={animalId}
>
{ /* Item.key not explicitly set */
(item) => <Item>{item.name}</Item>
}
</Picker>
<p>Animal id type: {typeof animalId}</p>
</>
);
}
The onSelectionChange
handler will pass a numeric key to setAnimalId
(consistent with the items data) and value
+ defaultValue
will work with values of type number
.
Item.key explicitly set
If the Item.key
prop is explicitly set e.g.
<Item key={item.id}>{item.name}</Item>}
The onSelectionChange
will pass a string id to setAnimalId
, and the value
+ defaultValue
props will require values of type string
in order to work.
🤔 Expected Behavior?
I would expect in the case of setting Item.key
prop explicitly:
onSelectionChange
should pass the actual value (not the stringified version) of the original id / key
value
, and defaultValue
should be able to match the id / key prop on the items instead of requiring them to be string values
😯 Current Behavior
When Item.key
prop is explicitly set
onSelectionChange
passes a stringified value of the original id / key
value
, and defaultValue
don’t match the items id / key prop unless they are stringified
💁 Possible Solution
No response
🔦 Context
We have a wrapper component that needs to explicitly set the Item.key
prop based on properties on the items
. In cases where these props are numeric
or boolean
types, this forces the input value
+ onSelectedValue
change handlers to work with strings which doesn’t match the original data.
🖥️ Steps to Reproduce
https://codesandbox.io/p/sandbox/modern-shadow-znqpwf?file=%2Fsrc%2FApp.js%3A22%2C25&layout=%257B%2522sidebarPanel%2522%253A%2522EXPLORER%2522%252C%2522rootPanelGroup%2522%253A%257B%2522direction%2522%253A%2522horizontal%2522%252C%2522contentType%2522%253A%2522UNKNOWN%2522%252C%2522type%2522%253A%2522PANEL_GROUP%2522%252C%2522id%2522%253A%2522ROOT_LAYOUT%2522%252C%2522panels%2522%253A%255B%257B%2522type%2522%253A%2522PANEL_GROUP%2522%252C%2522contentType%2522%253A%2522UNKNOWN%2522%252C%2522direction%2522%253A%2522vertical%2522%252C%2522id%2522%253A%2522clu8m4b1r00063b6qt0em6lmr%2522%252C%2522sizes%2522%253A%255B100%252C0%255D%252C%2522panels%2522%253A%255B%257B%2522type%2522%253A%2522PANEL_GROUP%2522%252C%2522contentType%2522%253A%2522EDITOR%2522%252C%2522direction%2522%253A%2522horizontal%2522%252C%2522id%2522%253A%2522EDITOR%2522%252C%2522panels%2522%253A%255B%257B%2522type%2522%253A%2522PANEL%2522%252C%2522contentType%2522%253A%2522EDITOR%2522%252C%2522id%2522%253A%2522clu8m4b1r00023b6qa3ncq69w%2522%257D%255D%257D%252C%257B%2522type%2522%253A%2522PANEL_GROUP%2522%252C%2522contentType%2522%253A%2522SHELLS%2522%252C%2522direction%2522%253A%2522horizontal%2522%252C%2522id%2522%253A%2522SHELLS%2522%252C%2522panels%2522%253A%255B%257B%2522type%2522%253A%2522PANEL%2522%252C%2522contentType%2522%253A%2522SHELLS%2522%252C%2522id%2522%253A%2522clu8m4b1r00033b6qnvq8g9lk%2522%257D%255D%252C%2522sizes%2522%253A%255B100%255D%257D%255D%257D%252C%257B%2522type%2522%253A%2522PANEL_GROUP%2522%252C%2522contentType%2522%253A%2522DEVTOOLS%2522%252C%2522direction%2522%253A%2522vertical%2522%252C%2522id%2522%253A%2522DEVTOOLS%2522%252C%2522panels%2522%253A%255B%257B%2522type%2522%253A%2522PANEL%2522%252C%2522contentType%2522%253A%2522DEVTOOLS%2522%252C%2522id%2522%253A%2522clu8m4b1r00053b6q8rg0tzb0%2522%257D%255D%252C%2522sizes%2522%253A%255B100%255D%257D%255D%252C%2522sizes%2522%253A%255B50%252C50%255D%257D%252C%2522tabbedPanels%2522%253A%257B%2522clu8m4b1r00023b6qa3ncq69w%2522%253A%257B%2522id%2522%253A%2522clu8m4b1r00023b6qa3ncq69w%2522%252C%2522tabs%2522%253A%255B%257B%2522id%2522%253A%2522clu8m4rnj00513b6q8rkyrj65%2522%252C%2522mode%2522%253A%2522permanent%2522%252C%2522type%2522%253A%2522FILE%2522%252C%2522initialSelections%2522%253A%255B%257B%2522startLineNumber%2522%253A22%252C%2522startColumn%2522%253A25%252C%2522endLineNumber%2522%253A22%252C%2522endColumn%2522%253A25%257D%255D%252C%2522filepath%2522%253A%2522%252Fsrc%252FApp.js%2522%252C%2522state%2522%253A%2522IDLE%2522%257D%255D%252C%2522activeTabId%2522%253A%2522clu8m4rnj00513b6q8rkyrj65%2522%257D%252C%2522clu8m4b1r00053b6q8rg0tzb0%2522%253A%257B%2522tabs%2522%253A%255B%257B%2522id%2522%253A%2522clu8m4b1r00043b6qt6yqaeqs%2522%252C%2522mode%2522%253A%2522permanent%2522%252C%2522type%2522%253A%2522UNASSIGNED_PORT%2522%252C%2522port%2522%253A0%252C%2522path%2522%253A%2522%252F%2522%257D%255D%252C%2522id%2522%253A%2522clu8m4b1r00053b6q8rg0tzb0%2522%252C%2522activeTabId%2522%253A%2522clu8m4b1r00043b6qt6yqaeqs%2522%257D%252C%2522clu8m4b1r00033b6qnvq8g9lk%2522%253A%257B%2522tabs%2522%253A%255B%255D%252C%2522id%2522%253A%2522clu8m4b1r00033b6qnvq8g9lk%2522%257D%257D%252C%2522showDevtools%2522%253Atrue%252C%2522showShells%2522%253Afalse%252C%2522showSidebar%2522%253Atrue%252C%2522sidebarPanelSize%2522%253A15%257D
Version
3.34.1
What browsers are you seeing the problem on?
Chrome
If other, please specify.
No response
What operating system are you using?
MacOS
🧢 Your Company/Team
No response
🕷 Tracking Issue
No response
Not ideal, but theoretically your items aren’t changing that frequently. So if you’re running into an issue, you could speed this up by doing your own caching so that you don’t have
{...item, key: item.fakeKey}
for every item. Also, this happens in linear time.@sookmax agreed this may be nice; I’m bringing it up with the team this week, hopefully.
Also of note for anyone coming across this, we do change this in the RAC version to use the id prop instead of the key prop. https://github.com/adobe/react-spectrum/blob/e6923d9473264b972974513dacbcfab5a24d5616/packages/react-aria-components/src/Collection.tsx#L677 apologies for the confusion.
I might be terribly wrong here, but I think it has to do with the fact that the
key
provided to<Item>
is essentially the underlying React Component’s key, and it’s extracted as-is inCollectionBuilder
:https://github.com/adobe/react-spectrum/blob/1293adf00a11ecac84ec9ab19af7dcea970af2b2/packages/%40react-stately/collections/src/CollectionBuilder.ts#L127
https://github.com/adobe/react-spectrum/blob/1293adf00a11ecac84ec9ab19af7dcea970af2b2/packages/%40react-stately/collections/src/CollectionBuilder.ts#L64-L84
Above,
element
passed as the first argument ofthis.getkey()
is the object representation of<Item key={item.id}>
(in our context) containing the providedkey
, and we extract this React key verbatim in the first if statement ofgetKey()
.Thing is, React always seems to convert the provided key to string. (I found this stack exchange answer). For example if you specify something like
<Item key={{hello: 'world'}}>
, you’ll see the element object would be of form:{$$typeof: Symbol(react.element), key: '[object Object]', ref: null, props: {…}, type: ƒ, …}