Skip to content

Make ListBox filter directly overridable #8084

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
vezaynk opened this issue Apr 11, 2025 · 8 comments
Open

Make ListBox filter directly overridable #8084

vezaynk opened this issue Apr 11, 2025 · 8 comments

Comments

@vezaynk
Copy link

vezaynk commented Apr 11, 2025

Provide a general summary of the feature here

It would be nice if the filtering here could be override directly on the ListBox declaratively. Like

// No filtering at all
<ListBox filter={null} />
// Custom filter
<ListBox filter={(item, input) => customFilter(item, input)} />

Right now, to achieve custom filtering, you need to setup the combox as a controlled component: https://react-spectrum.adobe.com/react-aria/useComboBox.html#custom-filtering

And then repeat the items you want in the context on both the Listbox and Combox. This introduces local state where you don't need to have it.

I'd be happy to make the PR if the team is okay with it

Alternatively, I can also see a solution where the filter is overridable on Combobox itself.

<Combobox filter={(item, input) => customFilter(item, input)} />

Another solution that I would enjoy seeing, would be enabling this pattern:

// Inside Combobox > Listbox > ListboxSection >
       <Collection
                items={item.options.filter((o) =>
                    contains(o.value, use(ComboBoxStateContext)?.inputValue ?? '') ||
                    contains(o.label, use(ComboBoxStateContext)?.inputValue ?? ''),
                )}

This work require being able to simply toggle the filtering on/off.

🤔 Expected Behavior?

Custom filtering on Comboboxes should not require local state

😯 Current Behavior

Custom filtering on Comboboxes requires local state

💁 Possible Solution

All a filter prop on Listbox OR on components that provide the filter currently (e.g. Combobox)

🔦 Context

I had to introduce local state because I wanted the filtering to react to the aria-label and some invisible metadata. I felt as though I was breaking a pattern by introducing local state.

💻 Examples

No response

🧢 Your Company/Team

No response

🕷 Tracking Issue

No response

@nwidynski
Copy link
Contributor

@vezaynk While I believe this feature is already on the roadmap and just waiting for Autocomplete to go stable, you can pretty much achieve what you are after today by hoisting the useListData(), which provides a filter method, up to the controller component and providing it via ListStateContext 👍

@vezaynk
Copy link
Author

vezaynk commented Apr 16, 2025

@nwidynski Would you accept outside contributions for this? This isn't a burning feature for us, but I'm interested in opportunities to dig into react-spectrum internals.

@nwidynski
Copy link
Contributor

Since #7959 looks like Automcomplete is coming out of beta, i will ping @LFDanLu here for more info on the stability of BaseCollection.UNSTABLE_filter.

@snowystinger
Copy link
Member

Wouldn't

// Inside Combobox > Listbox > ListboxSection >
       <Collection
                items={item.options.filter((o) =>
                    contains(o.value, use(ComboBoxStateContext)?.inputValue ?? '') ||
                    contains(o.label, use(ComboBoxStateContext)?.inputValue ?? ''),
                )}

Break the rules of hooks, can't call it conditionally, and calling it multiple times?
https://react.dev/reference/rules/rules-of-hooks#only-call-hooks-at-the-top-level

@vezaynk
Copy link
Author

vezaynk commented Apr 16, 2025

@snowystinger use can be called conditionally: https://react.dev/reference/react/use#reading-context-with-use

After sleeping on it, it doesn't look like a great pattern regardless. It was just one of the things that I tried.

@snowystinger
Copy link
Member

well, TIL, haven't gotten the chance to use use yet. Thanks for that.

@vezaynk
Copy link
Author

vezaynk commented Apr 16, 2025

Last note, I've actually found what I was looking for within defaultItems / defaultFilter by doing a search reconciling the checked item with its related object.

defaultItems={allDropdownOptions}
defaultFilter={(textValue, filter) => {
        const option = findItemByLabel(allDropdownOptions, textValue);
        if (option) {
          const { value, label } = option;
          return contains(value, filter) || contains(label, filter);
        }
        return false;
}}

@LFDanLu
Copy link
Member

LFDanLu commented Apr 17, 2025

With regards to BaseCollection.UNSTABLE_filter we still have an action item to refactor + relocate that logic out of BaseCollection, mainly around making it handle all kinds of arbitrary nodes and making it less specific to Menu/ListBox

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants