Skip to content

Commit

Permalink
[Autocomplete] Use getOptionLabel over stringify (#19974)
Browse files Browse the repository at this point in the history
  • Loading branch information
a-type committed Mar 4, 2020
1 parent 0048575 commit 2a0a935
Show file tree
Hide file tree
Showing 6 changed files with 44 additions and 29 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import React from 'react';
import TextField from '@material-ui/core/TextField';
import Autocomplete, { createFilterOptions } from '@material-ui/lab/Autocomplete';

const filter = createFilterOptions();
const filter = createFilterOptions<FilmOptionType>();

export default function FreeSoloCreateOption() {
const [value, setValue] = React.useState<FilmOptionType | null>(null);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import DialogActions from '@material-ui/core/DialogActions';
import Button from '@material-ui/core/Button';
import Autocomplete, { createFilterOptions } from '@material-ui/lab/Autocomplete';

const filter = createFilterOptions();
const filter = createFilterOptions<FilmOptionType>();

export default function FreeSoloCreateOptionDialog() {
const [value, setValue] = React.useState<FilmOptionType | null>(null);
Expand Down
10 changes: 8 additions & 2 deletions docs/src/pages/components/autocomplete/autocomplete.md
Original file line number Diff line number Diff line change
Expand Up @@ -137,16 +137,22 @@ You can use it to change the default option filter behavior.
import { createFilterOptions } from '@material-ui/lab/Autocomplete';
```

It supports the following options:
### `createFilterOptions(config) => filterOptions`

#### Arguments

1. `config` (*Object* [optional]):
- `config.ignoreAccents` (*Boolean* [optional]): Defaults to `true`. Remove diacritics.
- `config.ignoreCase` (*Boolean* [optional]): Defaults to `true`. Lowercase everything.
- `config.matchFrom` (*'any' | 'start'* [optional]): Defaults to `'any'`.
- `config.stringify` (*Func* [optional]): Defaults to `JSON.stringify`.
- `config.stringify` (*Func* [optional]): Controls how an option is converted into a string so that it can be matched against the input text fragment.
- `config.trim` (*Boolean* [optional]): Defaults to `false`. Remove trailing spaces.
- `config.limit` (*Number* [optional]): Default to null. Limit the number of suggested options to be shown. For example, if `config.limit` is `100`, only the first `100` matching options are shown. It can be useful if a lot of options match and virtualization wasn't set up.

#### Returns

`filterOptions`: the returned filter method can be provided directly to the `filterOptions` prop of the `Autocomplete` component, or the parameter of the same name for the hook.

In the following demo, the options need to start with the query prefix:

```js
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,14 @@ export interface CreateFilterOptionsConfig<T> {
limit?: number;
}

export interface FilterOptionsState {
export interface FilterOptionsState<T> {
inputValue: string;
getOptionLabel: (option: T) => string;
}

export function createFilterOptions<T>(
config?: CreateFilterOptionsConfig<T>,
): (options: T[], state: FilterOptionsState) => T[];
): (options: T[], state: FilterOptionsState<T>) => T[];

export interface UseAutocompleteCommonProps<T> {
/**
Expand Down Expand Up @@ -76,7 +77,7 @@ export interface UseAutocompleteCommonProps<T> {
* @param {object} state The state of the component.
* @returns {T[]}
*/
filterOptions?: (options: T[], state: FilterOptionsState) => T[];
filterOptions?: (options: T[], state: FilterOptionsState<T>) => T[];
/**
* If `true`, hide the selected options from the list box.
*/
Expand Down
26 changes: 4 additions & 22 deletions packages/material-ui-lab/src/useAutocomplete/useAutocomplete.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,35 +11,17 @@ function stripDiacritics(string) {
: string;
}

function defaultStringify(value) {
if (value == null) {
return '';
}

if (typeof value === 'string') {
return value;
}

if (typeof value === 'object') {
return Object.keys(value)
.map(key => value[key])
.join(' ');
}

return JSON.stringify(value);
}

export function createFilterOptions(config = {}) {
const {
ignoreAccents = true,
ignoreCase = true,
matchFrom = 'any',
stringify = defaultStringify,
stringify,
trim = false,
limit,
} = config;

return (options, { inputValue }) => {
return (options, { inputValue, getOptionLabel }) => {
let input = trim ? inputValue.trim() : inputValue;
if (ignoreCase) {
input = input.toLowerCase();
Expand All @@ -48,7 +30,7 @@ export function createFilterOptions(config = {}) {
input = stripDiacritics(input);
}
const filteredOptions = options.filter(option => {
let candidate = stringify(option);
let candidate = (stringify || getOptionLabel)(option);
if (ignoreCase) {
candidate = candidate.toLowerCase();
}
Expand Down Expand Up @@ -269,7 +251,7 @@ export default function useAutocomplete(props) {
}),
// we use the empty string to manipulate `filterOptions` to not filter any options
// i.e. the filter predicate always returns true
{ inputValue: inputValueIsSelectedValue ? '' : inputValue },
{ inputValue: inputValueIsSelectedValue ? '' : inputValue, getOptionLabel },
)
: [];

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { expect } from 'chai';
import { createFilterOptions } from './useAutocomplete';

describe('createFilterOptions', () => {
it('defaults to getOptionLabel for text filtering', () => {
const filterOptions = createFilterOptions();

const getOptionLabel = option => option.name;
const options = [
{
id: '1234',
name: 'cat',
},
{
id: '5678',
name: 'dog',
},
{
id: '9abc',
name: 'emu',
},
];

expect(filterOptions(options, { inputValue: 'a', getOptionLabel })).to.deep.equal([options[0]]);
});
});

0 comments on commit 2a0a935

Please sign in to comment.