reactjsmaterial-uimui-autocomplete

How to customize Material UI Autocomplete filter with createFilterOptions


I have an Material UI Autocomplete component, and I'm still learning some of its features. My basic implementation worked as expected, meaning that with this code:

import Autocomplete from '@mui/material/Autocomplete';
import { TextField } from '@mui/material';
import * as React from 'react';

const options = [
  {
    "value": {"name": "Bill Simmons HR"},
    "label": "BILL SIMMONS HR"
    },
]

const label = "Field"
export function MyAutocomplete() {
  return (
    <label>
      <Autocomplete
        sx={{width: 200}}
        options={options}
        id="custom-input-demo"
        renderInput={(params) => (
          <TextField
            {...params}
            label={label}
            placeholder={label}
          />
          )}
        />
    </label>
  );
}

...if I typed any of these, it worked as expected and an autocomplete option was visible:

"bill"  
"bill "  
"bill s"  
"bill simmons"  
"bill simmons hr"  
"simmons"  
"simmons "  

My problem is that I have a requirement that - in addition to the above functionality - an autocomplete visible option is supposed to appear by typing any of the below inputs, and so I also need these to work:

"simmons b"  
"simmons bill" 

With my basic implementation, the two inputs above cause any autocomplete option to disappear.

MUI docs suggest that to change the default option filter behavior, I should create a custom filter using the createFilterOptions factory. So, I did:

import Autocomplete, { createFilterOptions } from '@mui/material/Autocomplete';
import { TextField } from '@mui/material';
import * as React from 'react';

const options = [
  {
    "value": {"name": "Bill Simmons HR"},
    "label": "BILL SIMMONS HR"
    },
]

const SPACE = " "
function parse(label:string) {
  const arr = label.split(SPACE)
  return arr[1] + SPACE + arr[0] + SPACE + arr[2] // <<<--- enables new requirement, but disables original functionality
}

const filterOptions = createFilterOptions({
  matchFrom: 'start',
  stringify: (option:any) => parse(option.label),
})

const label = "Field"
export function MyAutocomplete() {
  return (
    <label>
      <Autocomplete
        filterOptions={filterOptions}
        sx={{width: 200}}
        options={options}
        id="custom-input-demo"
        renderInput={(params) => (
          <TextField
            {...params}
            label={label}
            placeholder={label}
          />
          )}
        />
    </label>
  );
}

And now this custom filter enables the new requirement, but it disables the original functionality.

What can I do to satisfy both use cases where all the below inputs display an autocomplete option?

"bill"
"bill "
"bill s"
"bill simmons"
"bill simmons hr"
"simmons"
"simmons "

"simmons b"
"simmons bill"

react:v18, muiv:5


Solution

  • Sounds like what you want is a fuzzy search, so you can see what you want regardless of the word order.

    I gave it a try and used the example from the "Advanced" section of the doc, with the example code that is linked in the "Advanced" section and was able to achieve what you are asking for --

    const filterOptions = (options, { inputValue }) => {
      const optionsForSearch = options.map((o) => o.label)
      const terms = inputValue.split(' ')
      const result = terms.reduceRight(
        (accu, term) => matchSorter(accu, term),
        optionsForSearch
      )
    Ï
      return result
    }
    

    In short, createFilterOptions won't be able to achieve what you are asking for simply due to it's limit (while still being very resourceful). So you need a even more custom filter in order to get the fuzzy search working.

    Here's the demo with part of your code and part of the code snippet inspired from the doc from MUI -- https://codesandbox.io/s/cool-river-f2m8gt?file=/src/Demo.tsx:333-602