Description
The Autocomplete component is a combination of an Input and a Dropdown, also called ComboBox. During typing, matching data items gets suggested in an option menu (listbox).
Typeahead and ComboBox
The Autocomplete component may also be known as Typeahead or ComboBox. But autocomplete describes the purpose more precisely and descriptive, therefore Eufemia is using this term.
When to use it:
Use it for both small autocomplete purposes and large (async) data set searches. The component supports two ways of showing ProgressIndicator.
You may check out the Dropdown component for more details on how to use it etc. They both share the same DrawerList.
Highlighting
Words found during typing are highlighted. The rules are:
- The two first words will match the beginning of a word
- The third word will match inside a word (can be changed with
search_in_word_index
) - Case insensitive
Using Components inside content
It is not possible to wrap them inside React Components. The reason is that the Autocomplete component needs to know what data it wants to search for before your React Component has rendered. But also, the component can't update the HTML to make the bold highlighting – "after" your component has rendered.
That means you can't run a component that will render as soon as it is displayed.
If you need to format numbers, then do it before you send in the data content.
It is possible to wrap your content inside one HTML Element. Nested elements are not supported.
To wrap your content only visually, you can provide your wrappers inside an array:
<Autocomplete data={[ { content: [ <IconPrimary icon="bell" key="item-1" />, <span className="custom-selector-a" key="item-2"> The Shawshank Redemption </span>, <span className="custom-selector-b" key="item-3"> The Dark Knight </span>, // etc. <NumberFormat value={1234} key="item-4" />, // <-- Not searchable nor highlightable ], }, ]} label="Label" />
or you can provide it inside a fragment:
<Autocomplete data={[ { content: ( <> <IconPrimary icon="bell" /> <span className="custom-selector-a"> The Shawshank Redemption </span> <span className="custom-selector-b">The Dark Knight</span> </> ), }, ]} label="Label" />
and if you need to decouple the searchable content from what's displayed, then you can put your searchable content inside search_content
:
<Autocomplete data={[ { content: ['your visual content'], search_content: ['your search content'], }, ]} label="Label" />
Re-render data
For performance optimization, you should ensure the data
array/object is memorized (with useMemo
, useState
or useRef
), so when the Autocomplete re-renders, it does not have to process the internal data unnecessary.
const MyComponent = () => {const data = React.useMemo(() => ['Item 1', 'Item 2'], [])return <Autocomplete data={data} />}
Or keep it outside the component:
const data = ['Item 1', 'Item 2']const MyComponent = () => {return <Autocomplete data={data} />}
Numbers
Numbers are often different than a word filter. You can use search_numbers={true}
to enable a number specialized filtering. See example in the demos.
Now the user could search for e.g. bank account numbers by just entering 201
, even if you format it like 2000 12 34567
(e.g. use format(20001234567, { ban: true })
from @dnb/eufemia/components/number-format/NumberUtils
).
Screen reader support
To enhance screen-reader usage, this component uses aria-live
to announce the number of options found (aria_live_options
).
Custom size
.dnb-autocomplete {--autocomplete-width: 20rem; /* custom width */}
You can also set the width directly, but then it has to be defined like so (including min-width
):
/** Because of the included label/status etc. we target the "__shell" */.dnb-autocomplete__shell {width: 10rem;}/** In order to change only the drawer-list width */.dnb-autocomplete .dnb-drawer-list__root {width: 10rem;}
Dynamically change data
You can manipulate the used data dynamically, either by changing the data
property or during user events like on_type
or on_focus
. The following properties and methods are there to use:
Methods
updateData
replace all data entries.emptyData
remove all data entries.resetSelectedItem
will invalidate the selected key.revalidateSelectedItem
will re-validate the internal selected key on the givenvalue
.revalidateInputValue
will re-validate the current input value and update it – based on the givenvalue
.setInputValue
update the input value.clearInputValue
will set the current input value to an empty string.focusInput
will set focus on the input element.showIndicator
shows a progress indicator instead of the icon (inside the input).hideIndicator
hides the progress indicator inside the input.showIndicatorItem
shows an item with a ProgressIndicator status as an data option item.showNoOptionsItem
shows the "no entries found" status as an data option item.setVisible
shows the DrawerList.setHidden
hides the DrawerList.showAllItems
shows all DrawerList items.setMode
switch the mode during runtime.debounce
a debounce method with a cancel invocation method on repeating calls. There is more documentation about this method.
Properties
dataList
contains all the data entries.
Example
<Autocompleteon_focus={({ updateData, showIndicator }) => {showIndicator()setTimeout(() => {updateData(topMovies)}, 1e3)}}on_type={({ value /* updateData, ... */ }) => {console.log('on_type', value)}}/>
Demos
Default autocomplete
<Autocomplete data={topMovies} label="Label" />
Autocomplete with numbers
<Autocomplete input_value="201" show_clear_button label="Label" data={numbersData} search_numbers={true} />
Autocomplete with a custom title
keep_value
means the input value gets not removed after an input blur happens.show_clear_button
means a clear button will show up when the input field contains a value.
<Autocomplete data={topMovies} keep_value={true} show_clear_button={true} label="Label" placeholder="Custom placeholder ..." on_change={({ data }) => { console.log('on_change', data) }} />
Async usage, dynamically update data during typing
This example simulates server delay with a timeout and - if it gets debounced, we cancel the timeout. Read more about the debounce method.
Also, you may consider using disable_filter
if you have a backend doing the search operation.
const onTypeHandler = ({ value, showIndicator, hideIndicator, updateData, debounce, /* ... */ }) => { console.log('typed value:', value) showIndicator() debounce( ({ value }) => { console.log('debounced value:', value) // simulate server delay const timeout = setTimeout(() => { // update the drawerList updateData(topMovies) hideIndicator() }, 600) // cancel invocation method return () => clearTimeout(timeout) }, { value, }, 250, ) } render( <Autocomplete mode="async" on_type={onTypeHandler} no_scroll_animation={true} placeholder="Search ..." />, )
Update data dynamically on the first focus
const onFocusHandler = ({ updateData, dataList, showIndicatorItem }) => { if (!dataList.length) { showIndicatorItem() setTimeout(() => { updateData(topMovies) }, 1e3) } } render( <Autocomplete mode="async" no_scroll_animation={true} prevent_selection={true} on_type={({ value /* updateData, ... */ }) => { console.log('on_type', value) }} on_focus={onFocusHandler} />, )
With a Button to toggle the open / close state
NB: Just to show the possibility; the data is given as a function.
<Autocomplete label="Label" value={10} show_submit_button={true} on_change={({ data }) => { console.log('on_change', data) }} > {() => topMovies} </Autocomplete>
With a predefined input/search value
<Autocomplete label="Label" input_value="the pa ther" no_animation on_change={({ data }) => { console.log('on_change', data) }} > {() => topMovies} </Autocomplete>
Different sizes
Four sizes are available: small
, default
, medium
and large
.
<Flex.Vertical> <Autocomplete label="Label" size="default" data={() => topMovies} /> <Autocomplete label="Label" size="medium" data={() => topMovies} /> <Autocomplete label="Label" size="large" data={() => topMovies} /> </Flex.Vertical>
Data suffix value
Data is provided as such:
const { locale } = React.useContext(Context)const data = [{suffix_value: (<NumberFormat currency srLabel="Total:" locale={locale}>{12345678}</NumberFormat>),selected_value: `Brukskonto (${ban})`,content: ['Brukskonto', ban],},]
const CustomWidth = styled(Autocomplete)` .dnb-drawer-list__root, .dnb-autocomplete__shell { width: 50vw; min-width: 15rem; max-width: 30rem; } ` render( <CustomWidth value={1} data={numbers} size="medium" input_icon={null} show_submit_button label="From account" label_direction="vertical" />, )
Custom width
const CustomWidthOne = styled(Autocomplete)` .dnb-autocomplete__shell { width: 10rem; } ` const CustomWidthTwo = styled(Autocomplete)` &.dnb-autocomplete--is-popup .dnb-drawer-list__root { width: 12rem; } ` const CustomWidthThree = styled(Autocomplete)` /** Change the "__shell" width */ .dnb-autocomplete__shell { width: 12rem; } /** Change the "__list" width */ .dnb-drawer-list__root { width: 20rem; } ` render( <Flex.Vertical> <CustomWidthOne label="Label" label_sr_only size="default" icon_position="left" data={topMovies} /> <CustomWidthTwo label="Label" label_sr_only size="medium" data={topMovies} /> <CustomWidthThree label="Label" label_sr_only size="large" align_autocomplete="right" icon_position="right" input_icon="bell" data={topMovies} /> </Flex.Vertical>, )
Autocomplete with status message
<Autocomplete data={topMovies} label="Label" status="Please select a valid date" status_state="info" show_submit_button />