Skip to content

[autocomplete] filter={null} silently ignored — falls back to default contains filter #4196

Description

@gatorcse

Bug report

Note: This bug was discovered and diagnosed with the assistance of an AI coding agent (Claude). The source analysis, minimal reproduction, and write-up were largely AI-generated, though reviewed and edited by a human before filing.

Problem

Passing filter={null} to <Autocomplete.Root> does not disable filtering. Instead, the default contains filter is silently applied, causing items to be filtered against the input text. This makes some forms of of server-side search unusable with the Autocomplete component when the input text doesn't substring-match the item labels (e.g., searching against multiple fields).

I later realized that filteredItems was a better approach for the server side filtering use case, but this is still a bug for the filter.

Root cause

In AutocompleteRoot.tsx, the filter prop is checked with a truthiness test:

const baseFilter = React.useMemo(() => {
  if (other.filter) {       // ← null is falsy, so this branch is skipped
    return other.filter;
  }
  return collator.contains;  // ← falls through to default filter
}, [other.filter, collator]);

null is falsy in JavaScript, so if (other.filter) evaluates to false, and baseFilter is set to collator.contains (the default case-insensitive string filter). The user's filter={null} is silently discarded.
By contrast, the underlying AriaCombobox primitive handles this correctly:

const filter = React.useMemo(() => {
  if (filterProp === null) {   // ← explicit null check
    return () => true;
  }
  // ...
}, [...]);

But AriaCombobox never sees null because AutocompleteRoot intercepts the prop and replaces it before passing it down (line 96: filter: resolvedFilter).

Suggested fix

Change the truthiness check to an explicit undefined check:

 const baseFilter = React.useMemo(() => {
-  if (other.filter) {
+  if (other.filter !== undefined) {
     return other.filter;
   }
   return collator.contains;
 }, [other.filter, collator]);

This way, filter={null} passes through to AriaCombobox as intended, and filter={undefined} (the default / not provided) still falls back to collator.contains.

Reproduction

// Items are fetched from a server — client-side filtering must be disabled.
<Autocomplete.Root
  items={serverResults}
  filter={null}              // ← expects no client-side filtering
  value={searchText}
  onValueChange={setSearchText}
  itemToStringValue={(item) => item.name}
>
  {/* ... */}
</Autocomplete.Root>

Expected: All items in serverResults appear in the dropdown regardless of input text.
Actual: Items are filtered by the default contains filter. If searchText doesn't substring-match any item.name, the dropdown appears empty even though items is non-empty.

Workaround

Pass a function instead of null:

const noFilter = () => true;
<Autocomplete.Root filter={noFilter} ... />

This is truthy, so AutocompleteRoot uses it as the filter function, and it passes all items through.

Environment

  • @base-ui/react: 1.2.0

Metadata

Metadata

Assignees

No one assigned

    Labels

    component: autocompleteChanges related to the autocomplete component.type: regressionA bug, but worse, it used to behave as expected.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions