Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Capturing Tab Key on Material UI AutoComplete Component

Here's a sandbox: https://codesandbox.io/s/wild-sea-h2i0m

Here's the code for the Autocomplete from that sandbox:

import React from "react";
import Autocomplete from "@material-ui/lab/Autocomplete";
import Chip from "@material-ui/core/Chip";
import CloseIcon from "@material-ui/icons/Close";
import TextField from "@material-ui/core/TextField";
import { FieldProps } from "formik";

const isEmailValid = (email: string) =>
  /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);

const EmailsField = ({
  field,
  form: { errors, touched, setTouched, setFieldValue },
  ...props
}: FieldProps) => {
  const name = field.name;
  const [value, setValue] = React.useState<string[]>([]);
  const [inputValue, setInputValue] = React.useState("");

  const handleChange = (event: React.ChangeEvent<{}>, emails: string[]) => {
    setTouched({ ...touched, [name]: true });
    setValue(emails);
    event.persist();
    setFieldValue(name, emails);
  };

  const handleInputChange = (
    event: React.ChangeEvent<{}>,
    newInputValue: string
  ) => {
    const options = newInputValue.split(/[ ,]+/);
    const fieldValue = value
      .concat(options)
      .map(x => x.trim())
      .filter(x => x);

    if (options.length > 1) {
      handleChange(event, fieldValue);
    } else {
      setInputValue(newInputValue);
    }
  };

  return (
    <Autocomplete<string>
      multiple
      disableClearable={true}
      options={[]}
      freeSolo
      renderTags={(emails, getTagProps) =>
        emails.map((email, index) => (
          <Chip
            deleteIcon={<CloseIcon />}
            variant="default"
            label={email}
            color={isEmailValid(email) ? "primary" : "secondary"}
            {...getTagProps({ index })}
          />
        ))
      }
      value={value}
      inputValue={inputValue}
      onChange={handleChange}
      onInputChange={handleInputChange}
      renderInput={params => (
        <TextField
          {...params}
          name={name}
          error={touched[name] && Boolean(errors.emails)}
          helperText={touched[name] && errors.emails}
          variant="outlined"
          InputProps={{ ...params.InputProps }}
          {...props}
        />
      )}
    />
  );
};

export default EmailsField;

I'm unable to make the value typed by the user become a single option after the user types and then presses the tab keyboard button to move to the submit button.

Any idea how?

like image 359
Sammy Avatar asked May 26 '20 12:05

Sammy


Video Answer


1 Answers

You can accomplish this by adding a handler for onKeyDown:

  const handleKeyDown = (event: React.KeyboardEvent) => {
    switch (event.key) {
      case "Tab": {
        if (inputValue.length > 0) {
          handleChange(event, value.concat([inputValue]));
        }
        break;
      }
      default:
    }
  };

and then adding that to the text input via inputProps:

      renderInput={params => {
        params.inputProps.onKeyDown = handleKeyDown;
        return (
          <TextField
            {...params}
            name={name}
            error={touched[name] && Boolean(errors.emails)}
            helperText={touched[name] && errors.emails}
            variant="outlined"
            {...props}
          />
        );
      }}

Here's the full new code for the EmailsField component from your sandbox:

import React from "react";
import Autocomplete from "@material-ui/lab/Autocomplete";
import Chip from "@material-ui/core/Chip";
import CloseIcon from "@material-ui/icons/Close";
import TextField from "@material-ui/core/TextField";
import { FieldProps } from "formik";

const isEmailValid = (email: string) =>
  /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);

const EmailsField = ({
  field,
  form: { errors, touched, setTouched, setFieldValue },
  ...props
}: FieldProps) => {
  const name = field.name;
  const [value, setValue] = React.useState<string[]>([]);
  const [inputValue, setInputValue] = React.useState("");

  const handleChange = (event: React.ChangeEvent<{}>, emails: string[]) => {
    setTouched({ ...touched, [name]: true });
    setValue(emails);
    event.persist();
    setFieldValue(name, emails);
  };

  const handleInputChange = (
    event: React.ChangeEvent<{}>,
    newInputValue: string
  ) => {
    const options = newInputValue.split(/[ ,]+/);
    const fieldValue = value
      .concat(options)
      .map(x => x.trim())
      .filter(x => x);

    if (options.length > 1) {
      handleChange(event, fieldValue);
    } else {
      setInputValue(newInputValue);
    }
  };

  const handleKeyDown = (event: React.KeyboardEvent) => {
    switch (event.key) {
      case "Tab": {
        if (inputValue.length > 0) {
          handleChange(event, value.concat([inputValue]));
        }
        break;
      }
      default:
    }
  };
  return (
    <Autocomplete<string>
      multiple
      disableClearable={true}
      options={[]}
      freeSolo
      renderTags={(emails, getTagProps) =>
        emails.map((email, index) => (
          <Chip
            deleteIcon={<CloseIcon />}
            variant="default"
            label={email}
            color={isEmailValid(email) ? "primary" : "secondary"}
            {...getTagProps({ index })}
          />
        ))
      }
      value={value}
      inputValue={inputValue}
      onChange={handleChange}
      onInputChange={handleInputChange}
      renderInput={params => {
        params.inputProps.onKeyDown = handleKeyDown;
        return (
          <TextField
            {...params}
            name={name}
            error={touched[name] && Boolean(errors.emails)}
            helperText={touched[name] && errors.emails}
            variant="outlined"
            {...props}
          />
        );
      }}
    />
  );
};

export default EmailsField;

Edit Autocomplete

Related answer: Material ui Autocomplete: can tags be created on events aside from 'Enter' events?

like image 178
Ryan Cogswell Avatar answered Oct 23 '22 19:10

Ryan Cogswell