Skip to content

Conversation

@atomiks
Copy link
Contributor

@atomiks atomiks commented Jun 26, 2025

@atomiks atomiks added component: select Changes related to the select component. type: new feature Expand the scope of the product to solve a new problem. labels Jun 26, 2025
@pkg-pr-new
Copy link

pkg-pr-new bot commented Jun 26, 2025

vite-css-base-ui-example

npm i https://pkg.pr.new/@base-ui-components/react@2173

commit: 136877c

@mui-bot
Copy link

mui-bot commented Jun 26, 2025

Bundle size report

Bundle Parsed Size Gzip Size
@base-ui-components/react 🔺+1.47KB(+0.48%) 🔺+480B(+0.49%)

Details of bundle changes

Generated by 🚫 dangerJS against 136877c

@netlify
Copy link

netlify bot commented Jun 26, 2025

Deploy Preview for base-ui ready!

Name Link
🔨 Latest commit 136877c
🔍 Latest deploy log https://app.netlify.com/projects/base-ui/deploys/686c91cc436e0000087998ea
😎 Deploy Preview https://deploy-preview-2173--base-ui.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@atomiks atomiks force-pushed the feat/select-multiple-2 branch from 93690f6 to 997ff3f Compare June 26, 2025 07:53
@atomiks atomiks force-pushed the feat/select-multiple-2 branch 2 times, most recently from 8d5e9ba to 901ea82 Compare June 26, 2025 11:31
@atomiks atomiks marked this pull request as ready for review June 26, 2025 11:34
@atomiks atomiks force-pushed the feat/select-multiple-2 branch from e342325 to c7ffa35 Compare June 26, 2025 11:43
@atomiks atomiks force-pushed the feat/select-multiple-2 branch from 81dfec0 to a814f66 Compare June 27, 2025 11:58
@atomiks atomiks force-pushed the feat/select-multiple-2 branch from a814f66 to 452ccfa Compare June 28, 2025 01:19
@github-actions github-actions bot added the PR: out-of-date The pull request has merge conflicts and can't be merged. label Jun 30, 2025
Signed-off-by: atomiks <cc.glows@gmail.com>
@github-actions github-actions bot removed the PR: out-of-date The pull request has merge conflicts and can't be merged. label Jun 30, 2025
Comment on lines 30 to 46
// Pre-compute a value → label map to enable O(1) look-ups when deriving the
// display label(s).
const valueLabelMap = React.useMemo(() => {
if (!items) {
return undefined;
}

if (Array.isArray(items)) {
const map = new Map<any, React.ReactNode>();
for (const { value: itemValue, label } of items) {
map.set(itemValue, label);
}
return items[value];
return map;
}

return new Map<any, React.ReactNode>(Object.entries(items));
}, [items]);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't insist on it, but this is a case where the store and reselect can help. You can put derived values in memoized selectors. Those are more efficient than React's useMemo.

const itemsSelector = state => state.items
const valueSelector = state => state.value
const multipleSelector = state => state.multiple

const valueLabelMapSelector = createSelectorMemoized(
  itemsSelector,
  items => { /* ... */ }
)

const labelFromItems = createSelectorMemoized(
  valueSelector,
  multipleSelector,
  valueLabelMapSelector,
  (value, multiple, valueLabelMap) => { /* ... */ }
)

That being said, also note that I'm not sure this code is actually beneficial as a performance optimization. The main issue is that we're adding the cost of building a Map (an O(n) cost) for each Select on mount. If you have a page with a large number of those components, you're actually increasing the page-load time.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Building a map could potentially be beneficial (or at least not harmful) when items is an array. Without the map, we'd need to find the label to display in the array, what also is O(n). However, when items is an object, it seems pointless.

Copy link
Contributor Author

@atomiks atomiks Jul 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I changed it so it only creates a map under this condition:

Array.isArray(items) && Array.isArray(value)

As this is the one that creates a loop within a loop to find the label for each value. It should also not be so common to have many of these on one page. I also think most will be using a custom renderer (children function) instead of using the default comma-separated behavior, which sidesteps this

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Building a map could potentially be beneficial (or at least not harmful)

That's false. Building a map upfront means you're forcing an O(n) cost to happen at mount time. This only saves time assuming that:

  • The user changes the value of the select multiple times (which I could argue is a rarer use-case than only setting its value once)
  • Every select on a page gets its value changed (otherwise, you're paying an O(n) cost for no reason), which is not necessarily true

It should also not be so common to have many of these on one page.

I feel like you're consistently thinking only of simple cases. If you want to build a general-purpose framework that can be used for a wide variety of cases, you need to consider complex/edge cases as well. For example, if a user wants Select in datagrid cells, I can guarantee you I'm going to be unhappy about the mount-time performance cost of creating those Map:s while the user is scrolling the grid - it directly competes with the virtualization logic for the available CPU time.

An unopened Select is basically a button component. It should not have an O(n) cost associated with it at mount-time.

Copy link
Contributor

@romgrk romgrk Jul 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And the reason why I said store selectors can be more efficient is that they would allow you to make that O(n) computation lazy, delaying it to until the map is needed for the first time. React.useMemo() forces the O(n) immediately. You would get all the benefits and no downsides if you moved that computation to a store selector (provided you don't useSelector it but call the selector directly when you need it).

Copy link
Contributor Author

@atomiks atomiks Jul 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The latest commit removes the map entirely as they always need to use a custom renderer for multiple selection. They'll use their own lookup map to retrieve each label for the values

"description": "Determines if the select enters a modal state when open.\n- `true`: user interaction is limited to the select: document page scroll is locked and and pointer interactions on outside elements are disabled.\n- `false`: user interaction with the rest of the document is allowed."
},
"multiple": {
"type": "false",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's not quite right. I'll see what's going on in the API extractor

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#2104 should fix it.

Comment on lines 30 to 46
// Pre-compute a value → label map to enable O(1) look-ups when deriving the
// display label(s).
const valueLabelMap = React.useMemo(() => {
if (!items) {
return undefined;
}

if (Array.isArray(items)) {
const map = new Map<any, React.ReactNode>();
for (const { value: itemValue, label } of items) {
map.set(itemValue, label);
}
return items[value];
return map;
}

return new Map<any, React.ReactNode>(Object.entries(items));
}, [items]);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Building a map could potentially be beneficial (or at least not harmful) when items is an array. Without the map, we'd need to find the label to display in the array, what also is O(n). However, when items is an object, it seems pointless.

@atomiks atomiks force-pushed the feat/select-multiple-2 branch 2 times, most recently from cc21462 to cd589f6 Compare July 3, 2025 06:50
@atomiks atomiks force-pushed the feat/select-multiple-2 branch from cd589f6 to d892008 Compare July 3, 2025 06:53
@github-actions github-actions bot added the PR: out-of-date The pull request has merge conflicts and can't be merged. label Jul 4, 2025
@ej-shafran
Copy link

Hey y'all - is there any update on this? What needs to be done to get this merged?

If needed, I can pick this up and help fix the relevant issues... Would really love to have this feature as it's very relevant for basically every project where I use a Select...

@github-actions github-actions bot removed the PR: out-of-date The pull request has merge conflicts and can't be merged. label Jul 7, 2025
Copy link
Member

@michaldudak michaldudak left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Aside from #2173 (comment), this looks OK. If you merge this before #2104, I'll fix the docs in that PR.

@atomiks atomiks force-pushed the feat/select-multiple-2 branch 2 times, most recently from afad197 to 96f2388 Compare July 7, 2025 13:09
@atomiks
Copy link
Contributor Author

atomiks commented Jul 8, 2025

Note on hidden <select> vs. hidden <input>s:

From what I can tell the hidden <select> requires all possible <option>s to be available upfront (the browser autofill test fails in particular if not), which would make the items prop required and also introduce some cost of creating all the <option> elements. The hidden inputs on the other hand don't have this issue

@atomiks atomiks force-pushed the feat/select-multiple-2 branch 2 times, most recently from eaee0c8 to dc9da7e Compare July 8, 2025 03:17
@atomiks atomiks force-pushed the feat/select-multiple-2 branch from dc9da7e to 136877c Compare July 8, 2025 03:34
@atomiks atomiks merged commit a0a0292 into mui:master Jul 8, 2025
21 checks passed
@atomiks atomiks deleted the feat/select-multiple-2 branch July 8, 2025 04:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

component: select Changes related to the select component. type: new feature Expand the scope of the product to solve a new problem.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[select] Support multiple selections

5 participants