From 2a0a9357ed944625ddcc5d3838b0b3ca546429ab Mon Sep 17 00:00:00 2001 From: Grant Forrest Date: Wed, 4 Mar 2020 06:52:49 -0500 Subject: [PATCH] [Autocomplete] Use getOptionLabel over stringify (#19974) --- .../autocomplete/FreeSoloCreateOption.tsx | 2 +- .../FreeSoloCreateOptionDialog.tsx | 2 +- .../components/autocomplete/autocomplete.md | 10 +++++-- .../src/useAutocomplete/useAutocomplete.d.ts | 7 ++--- .../src/useAutocomplete/useAutocomplete.js | 26 +++---------------- .../useAutocomplete/useAutocomplete.test.js | 26 +++++++++++++++++++ 6 files changed, 44 insertions(+), 29 deletions(-) create mode 100644 packages/material-ui-lab/src/useAutocomplete/useAutocomplete.test.js diff --git a/docs/src/pages/components/autocomplete/FreeSoloCreateOption.tsx b/docs/src/pages/components/autocomplete/FreeSoloCreateOption.tsx index 448d3fddc936b2..ed864cc9b47d43 100644 --- a/docs/src/pages/components/autocomplete/FreeSoloCreateOption.tsx +++ b/docs/src/pages/components/autocomplete/FreeSoloCreateOption.tsx @@ -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(); export default function FreeSoloCreateOption() { const [value, setValue] = React.useState(null); diff --git a/docs/src/pages/components/autocomplete/FreeSoloCreateOptionDialog.tsx b/docs/src/pages/components/autocomplete/FreeSoloCreateOptionDialog.tsx index 112c3da00513fb..f0c234cea1743a 100644 --- a/docs/src/pages/components/autocomplete/FreeSoloCreateOptionDialog.tsx +++ b/docs/src/pages/components/autocomplete/FreeSoloCreateOptionDialog.tsx @@ -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(); export default function FreeSoloCreateOptionDialog() { const [value, setValue] = React.useState(null); diff --git a/docs/src/pages/components/autocomplete/autocomplete.md b/docs/src/pages/components/autocomplete/autocomplete.md index 6ec7d561ca090f..31bbb4c9f959ed 100644 --- a/docs/src/pages/components/autocomplete/autocomplete.md +++ b/docs/src/pages/components/autocomplete/autocomplete.md @@ -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 diff --git a/packages/material-ui-lab/src/useAutocomplete/useAutocomplete.d.ts b/packages/material-ui-lab/src/useAutocomplete/useAutocomplete.d.ts index 12068959eb9c48..1929916312bc92 100644 --- a/packages/material-ui-lab/src/useAutocomplete/useAutocomplete.d.ts +++ b/packages/material-ui-lab/src/useAutocomplete/useAutocomplete.d.ts @@ -9,13 +9,14 @@ export interface CreateFilterOptionsConfig { limit?: number; } -export interface FilterOptionsState { +export interface FilterOptionsState { inputValue: string; + getOptionLabel: (option: T) => string; } export function createFilterOptions( config?: CreateFilterOptionsConfig, -): (options: T[], state: FilterOptionsState) => T[]; +): (options: T[], state: FilterOptionsState) => T[]; export interface UseAutocompleteCommonProps { /** @@ -76,7 +77,7 @@ export interface UseAutocompleteCommonProps { * @param {object} state The state of the component. * @returns {T[]} */ - filterOptions?: (options: T[], state: FilterOptionsState) => T[]; + filterOptions?: (options: T[], state: FilterOptionsState) => T[]; /** * If `true`, hide the selected options from the list box. */ diff --git a/packages/material-ui-lab/src/useAutocomplete/useAutocomplete.js b/packages/material-ui-lab/src/useAutocomplete/useAutocomplete.js index 2a2877593c8097..d98d1650945ef9 100644 --- a/packages/material-ui-lab/src/useAutocomplete/useAutocomplete.js +++ b/packages/material-ui-lab/src/useAutocomplete/useAutocomplete.js @@ -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(); @@ -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(); } @@ -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 }, ) : []; diff --git a/packages/material-ui-lab/src/useAutocomplete/useAutocomplete.test.js b/packages/material-ui-lab/src/useAutocomplete/useAutocomplete.test.js new file mode 100644 index 00000000000000..2af3c0cc1a98ff --- /dev/null +++ b/packages/material-ui-lab/src/useAutocomplete/useAutocomplete.test.js @@ -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]]); + }); +});