The best autocomplete widget around

I’m a huge fan of the Material-UI component library for React. I’ve used it in professional and personal projects for many years now, and it’s super sweet. Why is it so great?

Two things:

  • A wide variety of widgets that cover just about any UI you would want in a web site
  • Excellent documentation

There is also a large community of users so lots of good Stack Overflow questions and answers exist. Occasionally I’ve had to try and make their components do a new bit of magic, and it can be tough to wade through all that knowledge to determine if a specific use case will work. Recent case in point: figuring out how to replicate features of the Gmail To field.

My first step was considering using a chip input library for MUI I found earlier that would add chips to a standard MUI TextInput component. But it appeared that library was not really being maintained any more, and under the hood it used the pre-hook React lifecycle. So maybe not the best choice for new feature work. But I lucked out: there was an open issue in that repo that linked to an example that basically did what I wanted and bonus, it was in TypeScript.

This code sample used the MUI Autocomplete component, which is a solid way to get input from a list of preset options that supports type-ahead. Or it can be completely free-form where you can type anything you want in addition to a preset value. You can also configure it to allow inputting multiple values and then render special markup for those entries, as well as rendering whatever input component you want. All well and good. I was going to use it as the CC field on a page for sending email, and I had certain requirements:

  • Make sure each entry is a properly-formatted email address
  • Make sure the domain wasn’t in a blocklist
  • Do these validations when the user hit the Space key after typing an address

The first two were easy: we already had a regex to check the format of an address that worked for our purposes, plus code elsewhere to check if an address domain was in our blocklist. The tricky part was the Space key. The Autocomplete component has two props for its value: value and inputValue, which are tracked separately. In my configuration the former will have the list of email addresses, and the latter will have the value of the <input> element (in my implementation the list of addresses was actually a list of objects, each with the address and a validity flag).

To capture when the user inputs something, I handle the onChange event. The trigger for it is hitting the Enter key, which is good since I want users to be able to press that key to check the address they just typed. In order to capture any other keystrokes, I needed to handle the onKeydown event for the underlying TextField. The tricky part was telling it to use my custom handler.

The example I was following had a function for the renderInput prop. That function returned a TextField for use as the input portion. It included whatever props that were passed to renderInput, spreading the given object on to the TextField instance. It wasn’t readily apparent that any props were actually being set. Instead I spread my own object that included my key down event handler. And suddenly it became very broken, for a good reason: there were some props that Autocomplete needed to assign to the TextField and I was completely leaving them out.

The solution was to add my handler to the incoming object holding the required props, and assign that to the TextField, like so:

renderInput={(params) => {
    const newParms = {...params};

    newParams.onKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => {
        // Check for the Space key and handle it as needed
    };

    return (
        <TextField
            {...newParams}
            label="CC"
            placeholder="Enter email address"
        />
    );
}};

After that the regular Autocomplete features all worked and my Space key handler functioned as expected.