Autocomplete

A text input that suggests as you type. The value is whatever the reader typed, which is what separates it from a Select.

Usage

When to reach for AutoComplete over Select, Input and Mentions, and the decisions it leaves to you: where the options come from, who filters them, and what happens while a request is in flight.

When to use

A text field that suggests as you type. The value is whatever the reader typed, and the list is a shortcut to typing it rather than the set of answers you accept.
Reach for it when the field is free text:
  • A search box that offers recent queries, popular queries or completions, where pressing Enter on something nobody suggested is a normal thing to do.
  • An address, a city, a company or an email domain, where the suggestion saves typing but an unlisted one is still valid.
  • A tag or a category that mostly repeats but is allowed to be new.
  • A field whose list comes from a server and changes with the query, so there is no fixed set to render in the first place.
Use something else when
  • The value must come from the list. That is Select with showSearch. It keeps a chosen option, it can say "nothing selected", and it will not hand you a typo as a value. AutoComplete has no such state: there is only the text.
  • There is nothing to suggest. Use a plain Input. With no options the popup never opens, so AutoComplete adds nothing.
  • A trigger character opens the list. That is Mentions: the suggestion applies to one token inside a longer body of text, not to the whole field. AutoComplete always replaces the entire value.
  • More than one value is chosen. Select with mode="multiple" or tags. AutoComplete has no mode prop.

The value, and the options

value is a string the reader owns. options is a list you own, and the two are only related while a suggestion happens to match.
  • Recompute the options from the query. The normal shape is one state for the text and one for the list, with the list derived in onSearch or in an effect that watches the text. A static array is fine, but then turn on filterOption.
  • An option is { value, label }, and only value lands in the field. label takes any node, so a row can carry an icon, a count or a second line. What gets typed into the input is still the value string.
  • A row with its own options array is a group. { label: 'Recent', options: [...] } renders a heading with rows under it. Use it when the list mixes sources (recent, popular, from your team) and the source changes what the row means.
  • optionRender when the row needs the option's data. A rich label covers most of it. The render prop is for a row built from fields the label does not carry.
  • Nothing is pre-highlighted. defaultActiveFirstOption computes to false in this mode, so Enter submits what was typed rather than taking the first row. Set it true only if the first suggestion is the likeliest answer.

Filtering, and turning it off

filterOption defaults to false here, the opposite of Select's. Every option you pass is shown, in the order you passed it.
  • Server-backed search: leave it off. The query already filtered. Turning it on filters the server's answer a second time on the client, which is how a fuzzy or synonym match disappears the moment it comes back.
  • A local list: set it to true. That is a case-insensitive substring match against the option, enough for a list of cities or units.
  • A function for anything else, called as (input, option) => boolean. Lowercase both sides, or match on a field the label does not show. Keep it cheap: it runs once per option per keystroke.
  • Pass both inside showSearch, as showSearch={{ filterOption, onSearch }}. The top-level props still work but are deprecated.

onSearch, onChange and onSelect

Three callbacks fire around one interaction, and picking the wrong one is the most common bug on this component.
  • onSearch fires with the typed text. This is where a fetch belongs. It does not fire when a suggestion is clicked, so it will not re-query the thing that was just chosen.
  • onChange fires for both, every keystroke and every pick. Use it to hold the field's value, and nothing else. Its second argument is the matched option, or undefined when the text matches nothing, which is the cheapest way to ask "is this a real one?".
  • onSelect fires only when a suggestion is taken, by click or by Enter on an arrowed row. Navigation, opening a record and anything else with a side effect belongs here, not in onChange.
  • Do not fetch from onChange. It fires again with the chosen value when a suggestion is picked, so the list reloads underneath a reader who has just finished.

Bringing your own input

A single element as children replaces the built-in input. The popup and the keyboard behavior stay the same.
  • Input.TextArea is the usual reason: a multi-line composer that still suggests. Give it a fixed height or an autoSize range, because the popup is positioned against a field whose height now moves.
  • An Input with an addonBefore, addonAfter or a search button also works, and is how the field grows a submit affordance without leaving the component.
  • Do not pass size with a custom input. The child sizes itself, so set the size on the child.
  • Turn off autofill on your own input. If the browser's autofill list covers the suggestions, give it autoComplete="off".
  • Keep it to one element. Two children are read as legacy Option nodes, not as a trigger.

backfill and allowClear

  • backfill writes the arrowed row's value into the field as you move through the list, before anything is chosen. Only the keyboard triggers it, never hover, so the text does not jump under a moving mouse.
  • It suits a field where arrowing is the answer, a unit or a known city. It hurts a field where the reader is still composing, because it overwrites what they typed and Escape closes the popup without putting it back.
  • allowClear earns its place on a field that filters something else: clearing is the way back to the unfiltered view, and hunting Backspace for it is not. The icon comes from AntuikitProvider, so it follows the installed icon pack.
  • Clearing fires onClear and onChange with an empty value. Reset the list from one of them, or an empty field keeps the previous query's suggestions under it.

Debouncing, and the race

onSearch fires on every keystroke, and nothing in the component debounces it or orders the answers. Both are yours.
  • Debounce the query, not the text. Around 200 to 300ms is the usual range. The input stays controlled and instant. Only the fetch waits.
  • Responses arrive out of order, and the slow one wins. Type "ant" then "antd": if the first request is slower, its answer lands last and the reader sees suggestions for a query they have moved past. Keep a request id or the query string with each response and drop anything that is not the latest, or abort the previous request outright.
  • Do not disable the field while loading. It takes the focus out of the tab order mid-sentence. AutoComplete has no loading prop, so show the pending state in the popup instead.
  • Zero results shows nothing at all. notFoundContent is null by default, so a dead-end query closes the popup. Pass a node if "no matches" is worth saying.

Status and validation

  • status takes error or warning and recolors the border. Inside a Form.Item it is set from the field's own validation, so passing it by hand there fights the form.
  • Color is all it changes. The message still has to be written, next to the field, in words. A red border on its own says something is wrong and not what.
  • Validate the text, because that is the value. If only listed options are acceptable, use Select. If they are only preferred, say so in the message rather than blocking the submit.
  • Validate on blur or on submit, not on every keystroke, so the field does not turn red halfway through a word it is suggesting.