Import
import { Autocomplete } from '@contentful/f36-components';
import { Autocomplete } from '@contentful/f36-autocomplete';
Examples
Basic usage
The Autocomplete requires 3 props to work:
items
: It’s an array containing the items that will be shown as selectable options when the user types something in the TextInput
.onInputValueChange
: This function will be called every time the user types something in the input.
The component will pass the item
, which the filter method is currently iterating over, and the inputValue
prop of the TextInput
component.onSelectItem
: This function is called when the user selects one of the options of the list. The component will pass the selected item as an argument to the function.
An Autocomplete
with a list of spaces will look like this:
function AutocompleteBasicUsageExample() {
const spaces = [
'Travel Blog',
'Finnance Blog',
'Fitness App',
'News Website',
'eCommerce Catalogue',
'Photo Gallery',
];
const [selectedSpace, setSelectedSpace] = React.useState();
const [filteredItems, setFilteredItems] = React.useState(spaces);
const handleInputValueChange = (value) => {
const newFilteredItems = spaces.filter((item) =>
item.toLowerCase().includes(value.toLowerCase()),
);
setFilteredItems(newFilteredItems);
};
const handleSelectItem = (item) => {
setSelectedSpace(item);
};
return (
<Stack flexDirection="column" alignItems="start">
<Autocomplete
items={filteredItems}
onInputValueChange={handleInputValueChange}
onSelectItem={handleSelectItem}
/>
<Paragraph>
Selected space: <b>{selectedSpace}</b>
</Paragraph>
</Stack>
);
}
Using objects as items
We showed how to create an Autocomplete with an array of string but it’s also possible to use other types of data as items
.
A very common way of using the Autocomplete is with objects and for that, with a few changes to the previous example this can be done:
function AutocompleteWithObjectsExample() {
const spaces = [
{ name: 'Travel Blog', type: 'Community' },
{ name: 'Finnance Blog', type: 'Community' },
{ name: 'Fitness App', type: 'Medium' },
{ name: 'News Website', type: 'Medium' },
{ name: 'eCommerce Catalogue', type: 'Large' },
{ name: 'Photo Gallery', type: 'Large' },
];
const [selectedSpace, setSelectedSpace] = React.useState({ name: '' });
const [filteredItems, setFilteredItems] = React.useState(spaces);
const handleInputValueChange = (value) => {
const newFilteredItems = spaces.filter((item) =>
item.name.toLowerCase().includes(value.toLowerCase()),
);
setFilteredItems(newFilteredItems);
};
const handleSelectItem = (item) => {
setSelectedSpace(item);
};
return (
<Stack flexDirection="column" alignItems="start">
<Autocomplete
items={filteredItems}
onInputValueChange={handleInputValueChange}
onSelectItem={handleSelectItem}
itemToString={(item) => item.name}
renderItem={(item) => `${item.name} (${item.type})`}
/>
<Paragraph>
Selected fruit: <b>{selectedSpace.name}</b>
</Paragraph>
</Stack>
);
}
Both itemToString
and renderItem
are necessary when passing objects as items and they both will receive an "item" as an argument.
If you are using Typescript, you can tell the Autocomplete what is the type of your items to make these functions strongly typed.
You can do that by writing the component like this <Autocomplete<ItemType> {...props}/>
Highlighting an item with getStringMatch
A common use case for Autocomplete components is to highlight in each suggestion what is typed in the input.
Using the previous example, if a user types "fi" we want to show a list of suggestions where only "fi" is bold.
This is possible by using the renderItem
prop and the getStringMatch
utility function:
function AutocompleteHighlightingExample() {
const spaces = [
{ name: 'Travel Blog', type: 'Community' },
{ name: 'Finnance Blog', type: 'Community' },
{ name: 'Fitness App', type: 'Medium' },
{ name: 'News Website', type: 'Medium' },
{ name: 'eCommerce Catalogue', type: 'Large' },
{ name: 'Photo Gallery', type: 'Large' },
];
const [selectedSpace, setSelectedSpace] = React.useState({ name: '' });
const [filteredItems, setFilteredItems] = React.useState(spaces);
return (
<Stack flexDirection="column" alignItems="start">
<Autocomplete
items={filteredItems}
onInputValueChange={(value) => {
const newFilteredItems = spaces.filter((item) =>
item.name.toLowerCase().includes(value.toLowerCase()),
);
setFilteredItems(newFilteredItems);
}}
onSelectItem={(item) => setSelectedSpace(item)}
itemToString={(item) => item.name}
renderItem={(item, inputValue) => {
const { before, match, after } = getStringMatch(
item.name,
inputValue,
);
return (
<>
{before}
<b>{match}</b>
{after} ({item.type})
</>
);
}}
/>
<Paragraph>
Selected fruit: <b>{selectedSpace.name}</b>
</Paragraph>
</Stack>
);
}
Selecting multiple items
It is also possible to use the Autocomplete as multiselect. To improve the user experience, you can keep the dropdown open after selection by setting the "closeAfterSelect" property to false.
function AutocompleteMultiSelectionExample() {
const spaces = [
{ name: 'Travel Blog', type: 'Community' },
{ name: 'Finnance Blog', type: 'Community' },
{ name: 'Fitness App', type: 'Medium' },
{ name: 'News Website', type: 'Medium' },
{ name: 'eCommerce Catalogue', type: 'Large' },
{ name: 'Photo Gallery', type: 'Large' },
];
const [selectedFruits, setSelectedFruits] = React.useState([]);
const [filteredItems, setFilteredItems] = React.useState(spaces);
const handleInputValueChange = (value) => {
const newFilteredItems = spaces.filter((item) =>
item.name.toLowerCase().includes(value.toLowerCase()),
);
setFilteredItems(newFilteredItems);
};
const handleSelectItem = (item) => {
setSelectedFruits((prevState) => [...prevState, item.name]);
};
return (
<Stack flexDirection="column" alignItems="start">
<Autocomplete
items={filteredItems}
onInputValueChange={handleInputValueChange}
onSelectItem={handleSelectItem}
itemToString={(item) => item.name}
renderItem={(item) => item.name}
clearAfterSelect
closeAfterSelect={false}
/>
<span>
<Paragraph>Selected spaces:</Paragraph>
<ul>
{selectedFruits.map((fruit, index) => (
<li key={index}>{fruit}</li>
))}
</ul>
</span>
</Stack>
);
}
Using grouped objects as items
As an extension of "Use objects as items" section, you are also able to use a nested object to group your entries.
The most important part of making this work is the shape of the grouped object. The options themselves work exactly as in the object example and require the itemToString
and renderItem
functions.
Besides the correct shape of the object the Autocomplete component needs to receive the prop isGrouped
function AutocompleteWithGroupedItems() {
const spaces = [
{
groupTitle: 'Blogs',
options: [
{ name: 'Travel Blog', type: 'Community' },
{ name: 'Finnance Blog', type: 'Community' },
],
},
{
groupTitle: 'Others',
options: [
{ name: 'Fitness App', type: 'Medium' },
{ name: 'News Website', type: 'Medium' },
{ name: 'eCommerce Catalogue', type: 'Large' },
{ name: 'Photo Gallery', type: 'Large' },
],
},
];
const [selectedItem, setSelectedItem] = React.useState({ name: '' });
const [filteredItems, setFilteredItems] = React.useState(spaces);
const handleInputValueChange = (value) => {
const newFilteredItems = spaces.map((group) => {
return {
...group,
options: group.options.filter((item) =>
item.name.toLowerCase().includes(value.toLowerCase()),
),
};
});
setFilteredItems(newFilteredItems);
};
const handleSelectItem = (item) => {
setSelectedItem(item);
};
return (
<Stack flexDirection="column" alignItems="start">
<Autocomplete
items={filteredItems}
isGrouped
onInputValueChange={handleInputValueChange}
onSelectItem={handleSelectItem}
itemToString={(item) => `${item.name} (${item.type})`}
renderItem={(item) => `${item.name} (${item.type})`}
/>
<Paragraph>
Selected item from shopping list: {selectedItem.name}
</Paragraph>
</Stack>
);
}
Fully controlled selection from outside
In order to use proper form validation you need to be able to control the actual input field from the autocomplete, because the search query value is not the actual selection. This is done via the selectedItem property.
function ControlledAutocomplete() {
const spaces = [
{ name: 'Travel Blog', type: 'Community' },
{ name: 'Finnance Blog', type: 'Community' },
{ name: 'Fitness App', type: 'Medium' },
{ name: 'News Website', type: 'Medium' },
{ name: 'eCommerce Catalogue', type: 'Large' },
{ name: 'Photo Gallery', type: 'Large' },
];
const [selectedSpace, setSelectedSpace] = React.useState({ name: '' });
const [filteredItems, setFilteredItems] = React.useState(spaces);
const handleInputValueChange = (value) => {
const newFilteredItems = spaces.filter((item) =>
item.name.toLowerCase().includes(value.toLowerCase()),
);
setFilteredItems(newFilteredItems);
};
const handleSelectItem = (item) => {
setSelectedSpace(item);
};
return (
<Stack flexDirection="column" alignItems="start">
<Autocomplete
items={filteredItems}
onInputValueChange={handleInputValueChange}
onSelectItem={handleSelectItem}
selectedItem={selectedSpace}
itemToString={(item) => item.name}
renderItem={(item) => `${item.name} (${item.type})`}
/>
<Paragraph>
Selected Space: <b>{selectedSpace.name}</b>
<Button onClick={() => setSelectedSpace({ name: '' })}>
clear selection
</Button>
</Paragraph>
</Stack>
);
}
function AutocompleteWithErrorValidationExample() {
const spaces = [
'Travel Blog',
'Finnance Blog',
'Fitness App',
'News Website',
'eCommerce Catalogue',
'Photo Gallery',
];
const [selectedFruit, setSelectedFruit] = React.useState();
const [filteredItems, setFilteredItems] = React.useState(spaces);
const [isInvalid, setIsInvalid] = React.useState(false);
const handleInputValueChange = (value) => {
const newFilteredItems = spaces.filter((item) =>
item.toLowerCase().includes(value.toLowerCase()),
);
setFilteredItems(newFilteredItems);
};
const handleSelectItem = (item) => {
setSelectedFruit(item);
};
return (
<Stack flexDirection="column" alignItems="start">
<FormControl isInvalid={isInvalid}>
<FormControl.Label>Space:</FormControl.Label>
<Autocomplete
items={filteredItems}
onInputValueChange={handleInputValueChange}
onSelectItem={handleSelectItem}
/>
{isInvalid && (
<FormControl.ValidationMessage>Error</FormControl.ValidationMessage>
)}
</FormControl>
<Button onClick={() => setIsInvalid((prevState) => !prevState)}>
Toggle error message
</Button>
<Paragraph>
Selected space: <b>{selectedFruit}</b>
</Paragraph>
</Stack>
);
}
Fetching async data
function AutocompleteFetchingAsyncDataExample() {
const spaces = [
'Travel Blog',
'Finnance Blog',
'Fitness App',
'News Website',
'eCommerce Catalogue',
'Photo Gallery',
];
const [selectedFruit, setSelectedFruit] = React.useState();
const [isLoading, setIsLoading] = React.useState(false);
const [inputValue, setInputValue] = React.useState('');
const [items, setItems] = React.useState([]);
React.useEffect(() => {
setIsLoading(true);
const fetchSpaces = setTimeout(() => {
const filteredSpaces = spaces.filter((item) =>
item.toLowerCase().includes(inputValue.toLowerCase()),
);
setItems(filteredSpaces);
setIsLoading(false);
}, 800);
return () => clearTimeout(fetchSpaces);
}, [inputValue]);
const handleInputValueChange = (value) => {
setInputValue(value);
};
const handleSelectItem = (item) => {
setSelectedFruit(item);
};
return (
<Stack flexDirection="column" alignItems="start">
<Autocomplete
items={items}
onInputValueChange={handleInputValueChange}
onSelectItem={handleSelectItem}
isLoading={isLoading}
defaultValue={inputValue}
/>
<Paragraph>
Selected fruit: <b>{selectedFruit}</b>
</Paragraph>
</Stack>
);
}
Custom icon
Pass a custom icon to the text input, example: to indicate a search input
function AutocompleteBasicUsageExample() {
const spaces = [
'Travel Blog',
'Finnance Blog',
'Fitness App',
'News Website',
'eCommerce Catalogue',
'Photo Gallery',
];
const [selectedSpace, setSelectedSpace] = React.useState();
const [filteredItems, setFilteredItems] = React.useState(spaces);
const handleInputValueChange = (value) => {
const newFilteredItems = spaces.filter((item) =>
item.toLowerCase().includes(value.toLowerCase()),
);
setFilteredItems(newFilteredItems);
};
const handleSelectItem = (item) => {
setSelectedSpace(item);
};
return (
<Stack flexDirection="column" alignItems="start">
<Autocomplete
items={filteredItems}
icon={<SearchIcon variant="muted" />}
onInputValueChange={handleInputValueChange}
onSelectItem={handleSelectItem}
/>
<Paragraph>
Selected space: <b>{selectedSpace}</b>
</Paragraph>
</Stack>
);
}
Name | Type | Default |
---|
items required | T[] GenericGroupType<T>[] It’s an array of data to be used as "options" by the autocomplete component.
This can either be a plain list of items or a list of groups of items. | |
onSelectItem required | (item: T) => void This is the function that will be called when the user selects one of the "options" in the list.
The component will pass the selected "item" as an argument to the function.. | |
className | string CSS class to be appended to the root element | |
clearAfterSelect | false true If this is set to `true` the text input will be cleared after an item is selected | false |
closeAfterSelect | false true If this is set to `false` the dropdown menu will stay open after selecting an item | true |
defaultValue | string Set's default value for text input | |
icon | ReactElement<any, string | JSXElementConstructor<any>> Set a custom icon for the text input | |
id | string Sets the id of the input | |
inputRef | (instance: HTMLInputElement) => void RefObject<HTMLInputElement> Use this prop to get a ref to the input element of the component | |
isDisabled | false true Applies disabled styles | false |
isGrouped | false true Tells if the item is a object with groups | |
isInvalid | false true Applies invalid styles | false |
isLoading | false true Sets the list to show its loading state | false |
isReadOnly | false true Applies read-only styles | false |
isRequired | false true Validate the input | false |
itemToString | (item: T) => string When using objects as `items`, we recommend passing a function that tells Downshift how to extract a string
from those objetcs to be used as inputValue | |
listMaxHeight | number It sets the max-height, in pixels, of the list
The default value is the height of 5 single line items | 180 |
listRef | (instance: HTMLUListElement) => void RefObject<HTMLUListElement> Use this prop to get a ref to the list of items of the component | |
listWidth | "auto" "full" It sets the width of the list | "auto" |
noMatchesMessage | string A message that will be shown when it is not possible to find any option that matches the input value | "No matches" |
onBlur | (event: FocusEvent<HTMLInputElement, Element>) => void Function called when the input is blurred
@param event | |
onFocus | (event: FocusEvent<HTMLInputElement, Element>) => void Function called when the input is focused
@param event | |
onInputValueChange | (value: string) => void Function called whenever the input value changes | |
placeholder | string This is the value will be passed to the `placeholder` prop of the input. | "Search" |
renderItem | (item: T, inputValue: string) => ReactNode This is the function that will be called for each "item" passed in the `items` prop.
It receives the "item" and "inputValue" as arguments and returns a ReactNode.
The inputValue is passed in case you want to highlight the match on the render. | |
selectedItem | T Applying the selectedItem property turns autocomplete into a controlled component.
Can be used to display e.g. previously selected element. If it is an object the itemToString function will apply to it. | |
showEmptyList | false true Defines if the list should be shown even if empty, when input is focused | false |
testId | string A [data-test-id] attribute used for testing purposes | |
toggleRef | (instance: HTMLButtonElement) => void RefObject<HTMLButtonElement> Use this prop to get a ref to the toggle button of the component | |
usePortal | false true Boolean to control whether or not to render the suggestions box in a React Portal.
Rendering content inside a Portal allows the suggestions box to escape the bounds
of its parent while still being positioned correctly.
Defaults to `false` | |
Content guidelines
- Autocomplete label should be short, contain 1 to 3 words
- Label should be written in a sentence case (the first word capitalized, the rest lowercase)
Accessibility
- dismisses the dropdown when selecting with the enter key