Demystifying Errors in MUI Autocomplete — part — I

Autocomplete is the new beast in the Material UI core package.

It is the search cum input component. For search, we need to give options to search. Therefore ‘options’ prop is mandatory. With respect to the input field, Autocomplete is an abstract input field. We need to specify what input component needs to be rendered and in most cases, it would be TextField.Therefore ‘renderInput’ component is mandatory.

Autocomplete → Search and Input → options and renderInput props respectively

Options props take only arrays. Almost all Autocomplete errors I got is because of the inconsistency of data structures which we will see later. If the options array contains primitive values, it will directly display those values in the options list.

import * as React from "react";
import TextField from "@mui/material/TextField";
import Autocomplete from "@mui/material/Autocomplete";export default function ComboBox() {return (<Autocomplete
options={top100Films}
sx={{ width: 300 }}
renderInput={(params) => <TextField {...params} label="Movie" />}
/>
);
}const top100Films = [
"The Shawshank Redemption",
"The Godfather",
"The Godfather: Part II",
"The Dark Knight",
"12 Angry Men",
"Schindler's List",
"Pulp Fiction",
"The Lord of the Rings: The Return of the King",
"The Good, the Bad and the Ugly",
"Fight Club",
"The Lord of the Rings: The Fellowship of the Ring",
"Star Wars: Episode V - The Empire Strikes Back",
"Forrest Gump",
"Inception",
];

In the above example, the options prop takes an array. This array contains string(primitive data type) values of movies.


Demo for Options prop

What if an array contains objects. By default, it accepts the following structure.

options =  [
  { label: 'The Shawshank Redemption', year: 1994 },
  { label: 'The Godfather', year: 1972 },
  { label: 'The Godfather: Part II', year: 1974 },
  { label: 'The Dark Knight', year: 2008 },
  { label: '12 Angry Men', year: 1957 },
  { label: "Schindler's List", year: 1993 },
  { label: 'Pulp Fiction', year: 1994 },
]

It will display the label property in the object as a list of options. However, we can use different options structures by using getOptionLabel prop.

options =  [
  { name:'The Shawshank Redemption', year: 1994 },
  { name:'The Godfather', year: 1972 },
  { name: 'The Godfather: Part II', year: 1974 },
  { name: 'The Dark Knight', year: 2008 },
  { name: '12 Angry Men', year: 1957 },
  { name: "Schindler's List", year: 1993 },
  { name: 'Pulp Fiction', year: 1994 },
]<Autocomplete
options={top100Films}
getOptionLabel = {option => option.name}
sx={{ width: 300 }}
renderInput={(params) => <TextField {...params} label="Movie" />}
/>
);
}

getOptionlabel expects a function that has a default parameter and this parameter is basically individual options or each object in an array.


Demo for getoptionlabel

One thing about getOptionLabel is it doesn’t change the input value. It only alters the label which we see in the list. The input value is still the whole individual object. This fact needs to etch in your mind because knowing this would avoid 60% of errors.

Most of the props in Autocomplete are based on the relationship with options prop. Specifically Referential relationship with options.

import * as React from "react";
import Box from "@mui/material/Box";
import TextField from "@mui/material/TextField";
import Autocomplete from "@mui/material/Autocomplete";export default function CountrySelect() {return (<Autocomplete
id="country-select-demo"
sx={{ width: 300 }}
options={countryStrings}
autoHighlight
value={"Andorra"}
renderInput={(params) => (
<TextField
{...params}
label="Choose a country"
inputProps={{
...params.inputProps,
autoComplete: "new-password" // disable autocomplete and autofill
}}
/>
)}
/>
);
}const countries = [
{ code: "AD", label: "Andorra", phone: "376" },
{ code: "AI", label: "Anguilla", phone: "1-264" },
{ code: "AL", label: "Albania", phone: "355" },
{ code: "AM", label: "Armenia", phone: "374" },
{ code: "AO", label: "Angola", phone: "244" },
{ code: "AQ", label: "Antarctica", phone: "672" },
{ code: "AR", label: "Argentina", phone: "54" },
{ code: "AS", label: "American Samoa", phone: "1-684" },
{ code: "AT", label: "Austria", phone: "43" }
];
*-----Extracting only label from countries array----*const countryStrings = countries.map((option) => option.label);console.log(countryStrings);
["Andorra", "Anguilla", "Albania", "Armenia", "Angola", "Antarctica", "Argentina", "American Samoa", "Austria"]

How referential relationship works? Let us see some cases here.Options with countryStrings and countries.

countryStrings consist of array of strings.Let’s see what happens when

Case 1: options = {countryStrings} value = ‘Andorra’.

The above case works fine because the value we entered ‘Andorra’ matches with one of the values of countryStrings.

But when we change the value slightly, things get interesting.

Case 2 : options = {countryStrings} value = ‘andorra’.

The above case would throw an error but the value displayed at the textbox would be ‘andorra’ but we would get an error in console.

The value displayed is ‘andorra’

Error in console



This error occurred because ‘andorra’ doesn’t match with any of the strings in countryStrings

So rule no 1 is value must exactly match with any one of the options. Technically it must have referential equality with one of the options.

Case 3: Change the data structure itself

options =countries getOptionLabel = {option => option.name} value=’Andorra’

Instead of label property in countries let us change to name property.

import * as React from "react";
import Box from "@mui/material/Box";
import TextField from "@mui/material/TextField";
import Autocomplete from "@mui/material/Autocomplete";export default function CountrySelect() {
return (
<Autocomplete
id="country-select-demo"
sx={{ width: 300 }}
options={countries}
// value={"Andorra"}
renderInput={(params) => (
    <TextField {...params}
      label="Choose a country"
}}
/>
)}
/>
);
}const countries = [
{ code: "AD", name: "Andorra", phone: "376" },
{ code: "AI", name: "Anguilla", phone: "1-264" },
{ code: "AL", name: "Albania", phone: "355" },
{ code: "AM", name: "Armenia", phone: "374" },
{ code: "AO", name: "Angola", phone: "244" },
{ code: "AQ", name: "Antarctica", phone: "672" },
{ code: "AR", name: "Argentina", phone: "54" },
{ code: "AS", name: "American Samoa", phone: "1-684" },
{ code: "AT", name: "Austria", phone: "43" }
];

Without getOptionLabel and label property, we would get an error

Error when no label property in props




Notice getOptionLabel is invoked even though we added it in props.This is because of the default behaviour of getting ‘label’ property.

<Autocomplete
id="country-select-demo"
sx={{ width: 300 }}
options={countries}
getOptionLabel={(option) => option.name}
//value={"Andorra"}
renderInput={(params) => (
    <TextField {...params}
      label="Choose a country"
}}
/>
)}
/>

Now we don’t get any errors because we explicitly mention getting the label from the name property by getOptionLabel={(option) => option.name}

Let us add value to the props

<Autocomplete
id="country-select-demo"
sx={{ width: 300 }}
options={countries}
getOptionLabel={(option) => option.name}
value={"Andorra"}
renderInput={(params) => (
    <TextField {...params}
      label="Choose a country"
}}
/>
)}
/>



Error when value = ‘Andorra ’ is given

Here we would get an error like above. This is the tricky part. See the flow is

The value given to props is ‘string’ but each individual option is an ‘object’.Here value is hardcoded and hence it tries to sit directly into the DOM. Since we mention getOptionLabel, it will go through this step.getOptionLabel expects an object but the value provided is ‘string’.This is actually causing an error.

MUI: The `getOptionLabel` method of Autocomplete returned undefined instead of a string for “Andorra”.

So the rule no:2 is simple — just maintain the same data structures across the props.

So the correct way would be

<Autocomplete
id="country-select-demo"
sx={{ width: 300 }}
options={countries}
getOptionLabel={(option) => option.name}
value={countries[0]}
renderInput={(params) => (
    <TextField {...params}
      label="Choose a country"
}}
/>
)}
/>

When value meets the criteria of the same data structure

Illustration using canva