4

I want the users to be able to select multiple tags while also allowing them to add a tag if it does not exist, the examples on the material UI documentation work on the freeSolo option which works on string / object values as options whereas when we use multiple, that changes to an array

How do I implement a multiple creatable with material-ui?

My code:

// Fetch Adding tag list
const [listOpen, setListOpen] = useState(false);
const [options, setOptions] = useState<Tag[]>([]);
const loadingTags = listOpen && options.length === 0;

useEffect(() => {
  let active = true;

  if (!loadingTags) {
    return undefined;
  }

  (async () => {
    try {
      const response = await getAllTagsForUser();
      if (active) {
        setOptions(response.data);
      }
    } catch (error) {
      console.log(error);
    }
  })();

  return () => {
    active = false;
  };
}, [loadingTags]);

useEffect(() => {
  if (!listOpen) {
    setOptions([]);
  }
}, [listOpen]);


<Autocomplete
  multiple
  id="tags"
  open={listOpen}
  onOpen={() => {
    setListOpen(true);
  }}
  onClose={() => {
    setListOpen(false);
  }}
  options={options}
  disableCloseOnSelect
  getOptionLabel={(option) => option?.name || ""}
  defaultValue={
    contact?.tags?.map((element) => {
      return { name: element };
    }) || undefined
  }
  renderOption={(option, { selected }) => (
    <React.Fragment>
      <Checkbox
        icon={icon}
        checkedIcon={checkedIcon}
        style={{ marginRight: 8 }}
        checked={selected}
      />
      {option.name}
    </React.Fragment>
  )}
  style={{ width: 500 }}
  renderInput={(params) => (
    <TextField {...params} variant="outlined" label="Tags" />
  )}
/>;

This is just fetching tags from the server and showing them as options, I understand that to be able to allow adding more, I would need to add filterOptions and onChange but, can someone please provide an example on how to deal with array there?

1
  • Did you find the solution? Commented Sep 4, 2024 at 15:03

2 Answers 2

2

I know this isn't an quick answer but may someone else could use it. Found this Question buy searching an solution. Didn't find one so I tryed myself and this is what I Created and seems it works.

Based on the original Docs https://mui.com/components/autocomplete/#creatable

Complete example:

import React, { useEffect, useState } from "react";

//Components 
import TextField from "@mui/material/TextField";
import Autocomplete, { createFilterOptions } from "@mui/material/Autocomplete";

//Icons

const filter = createFilterOptions();

export default function AutocompleteTagsCreate() {
    const [selected, setSelected] = useState([])
    const [options, setOptions] = useState([]);

    useEffect(() => {
        setOptions(data);
    }, [])

    return (
        <Autocomplete
            value={selected}
            multiple
            onChange={(event, newValue, reason, details) => {
                let valueList = selected;
                if (details.option.create && reason !== 'removeOption') {
                    valueList.push({ id: undefined, name: details.option.name, create: details.option.create });
                    setSelected(valueList);
                }
                else {
                    setSelected(newValue);
                }
            }}
            filterSelectedOptions
            filterOptions={(options, params) => {
                const filtered = filter(options, params);

                const { inputValue } = params;
                // Suggest the creation of a new value
                const isExisting = options.some((option) => inputValue === option.name);
                if (inputValue !== '' && !isExisting) {
                    filtered.push({
                        name: inputValue,
                        label: `Add "${inputValue}"`,
                        create: true
                    });
                }

                return filtered;
            }}
            selectOnFocus
            clearOnBlur
            handleHomeEndKeys
            id="tags-Create"
            options={options}
            getOptionLabel={(option) => {
                // Value selected with enter, right from the input
                if (typeof option === 'string') {
                    return option;
                }
                // Add "xxx" option created dynamically
                if (option.label) {
                    return option.name;
                }
                // Regular option
                return option.name;
            }}
            renderOption={(props, option) => <li {...props}>{option.create ? option.label : option.name}</li>}
            freeSolo
            renderInput={(params) => (
                <TextField {...params} label="Tags" />
            )}
        />
    );
}

const data = [
    {
        id: 1,
        name: 'Tag1'
    },
    {
        id: 2,
        name: 'Tag2'
    },
    {
        id: 3,
        name: 'Tag3'
    },
    {
        id: 4,
        name: 'Tag4'
    },
]
Sign up to request clarification or add additional context in comments.

Comments

0

@4source's solution had some errors for me, fixed it:

import React, { useEffect, useState } from "react";

import TextField from "@mui/material/TextField";
import Autocomplete, { createFilterOptions } from "@mui/material/Autocomplete";

type Options = {
  id?: number;
  label?: string;
  name: string;
  create?: boolean;
};

const filter = createFilterOptions<Options>();

export default function MultiSelectCreatable() {
  const [selected, setSelected] = useState<Options[]>([]);
  const [options, setOptions] = useState<Options[]>([]);

  useEffect(() => {
    setOptions(data);
  }, []);
    
  return (
    <Autocomplete
      value={selected}
      multiple
      onChange={(event, newValue, reason, details) => {
        if (details?.option.create && reason !== "removeOption") {
          setSelected([...selected, {
            id: undefined,
            name: details.option.name,
            create: details.option.create,
          }]);
        } else {
            setSelected(newValue.map(value => {
                if (typeof value === "string") {
                    return {
                        id: undefined,
                        name: value,
                        create: true,
                      }
                } else {
                    return value
                }
            }));
        }
      }}
      filterSelectedOptions
      filterOptions={(options, params): Options[] => {
        const filtered = filter(options, params);

        const { inputValue } = params;
        // Suggest the creation of a new value
        const isExisting = options.some((option) => inputValue === option.name);
        if (inputValue !== "" && !isExisting) {
          filtered.push({
            name: inputValue,
            label: `Add "${inputValue}"`,
            create: true,
          });
        }

        return filtered;
      }}
      selectOnFocus
      clearOnBlur
      handleHomeEndKeys
      id="tags-Create"
      options={options}
      getOptionLabel={(option) => {
        // Value selected with enter, right from the input
        if (typeof option === "string") {
          return option;
        }
        // Add "xxx" option created dynamically
        if (option.label) {
          return option.name;
        }
        // Regular option
        return option.name;
      }}
      renderOption={(props, option) => (
        <li {...props}>{option.create ? option.label : option.name}</li>
      )}
      freeSolo
      renderInput={(params) => <TextField {...params} label="Tags" />}
    />
  );
}

const data = [
  {
    id: 1,
    name: "Tag1",
  },
  {
    id: 2,
    name: "Tag2",
  },
  {
    id: 3,
    name: "Tag3",
  },
  {
    id: 4,
    name: "Tag4",
  },
];

1 Comment

You know how to add two creatable options?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.