react-tag-autocomplete icon indicating copy to clipboard operation
react-tag-autocomplete copied to clipboard

Ability to add new tags automatically when pasting text on the input

Open mercera opened this issue 1 year ago • 4 comments
trafficstars

As mentioned in the title it would be nice to have an option to add tags automatically when pasting on the input. Current behaviour is to paste the text and press enter or click on add "tag name" which appears in the suggestion list to add the new tag. I have tried to use onInput prop to achieve this functionality but the callback function does not seem to return the event object, Therefore there is no way to know if the input is from pasting or just from typing. :)

mercera avatar Aug 07 '24 12:08 mercera

One way you can achieve that is by using the renderInput prop.

Probably over engineered, but here is an example of how you could achieve it.

I hope that helps.

import React, { useCallback, useRef, useState } from "react";
import { v4 as uuidv4 } from "uuid";
import {
  InputRendererProps,
  ReactTags,
  ReactTagsAPI,
  TagSelected,
} from "react-tag-autocomplete"; // Importing components and types from 'react-tag-autocomplete'
import { suggestions } from "./countries";
import "./styles.css";

export default function App() {
  const api = useRef<ReactTagsAPI>(null); // Ref to hold the instance of ReactTagsAPI
  const [selected, setSelected] = useState<TagSelected[]>([]); // State to manage the selected tags

  // Function to validate if a new tag can be added
  const onValidate = (value: string) => {
    return (
      selected.filter(
        (tag) => tag.label.toLocaleLowerCase() === value.toLocaleLowerCase()
      ).length === 0
    );
  };

  // Function to add a new tag to the list
  const onAdd = useCallback(
    (newTag: TagSelected) => {
      setSelected([...selected, newTag]);
    },
    [selected] // Dependency array ensures onAdd function has the latest state
  );

  // Function to remove a tag from the list
  const onDelete = useCallback(
    (tagIndex: number) => {
      setSelected(selected.filter((_, i) => i !== tagIndex));
    },
    [selected] // Dependency array ensures onDelete function has the latest state
  );

  // Function to handle paste events and add tags from clipboard data
  const onPaste = useCallback(
    (e: React.ClipboardEvent<HTMLInputElement>) => {
      const event = e;
      const clipboardData = event.clipboardData;
      const pastedText = clipboardData?.getData("text").trim() || "";
      const existingTagSuggestion = suggestions.find(
        (suggestion) =>
          suggestion.label.toLocaleLowerCase() ===
          pastedText.toLocaleLowerCase()
      );

      if (onValidate(pastedText)) {
        existingTagSuggestion
          ? setSelected([...selected, existingTagSuggestion])
          : setSelected([...selected, { value: uuidv4(), label: pastedText }]);

        // Clear the input field after pasting.
        // There is probably a better way to do this.
        setTimeout(() => {
          if (api?.current?.input?.value) {
            api.current.input.value = "";
          }
        }, 5);
      }
    },
    [selected] // Dependency array ensures onDelete function has the latest state
  );

  // Custom input component for rendering the input field
  function customInput({
    classNames,
    inputWidth,
    ...inputProps
  }: InputRendererProps) {
    return (
      <input
        className={classNames.input}
        style={{ width: inputWidth }}
        onPaste={onPaste} // Attach custom paste handler
        {...inputProps} // Spread other input properties
      />
    );
  }

  return (
    <ReactTags
      ref={api} // Set the ref to ReactTagsAPI instance
      allowNew // Allow new tags that are not in suggestions
      activateFirstOption // Automatically activate the first suggestion
      labelText="Select tags" // Label for the input field
      selected={selected} // Array of selected tags
      suggestions={suggestions} // Array of suggestion tags
      onAdd={onAdd} // Handler to add a tag
      onDelete={onDelete} // Handler to delete a tag
      onValidate={onValidate} // Handler to validate a new tag
      renderInput={customInput} // Custom input rendering
      noOptionsText="No matching tags" // Text to show when no options match
    />
  );
}

ellunium avatar Aug 07 '24 22:08 ellunium

Thanks 👍. Will try this out.

mercera avatar Aug 08 '24 04:08 mercera

Thanks for your question @mercera and your detailed response @ellunium.

There have been multiple discussions about adding similar behaviour to earlier versions of this component, however each time we found that the expected behaviour would change depending on the context, so no consensus was ever reached. Perhaps we could add an onPaste callback to at least make it a little easier to add custom logic?

i-like-robots avatar Aug 09 '24 07:08 i-like-robots

I have tried out @ellunium's solution and it's working perfectly. Thanks a lot btw :). @i-like-robots yes. I like that idea as well since that would make things much easier.

mercera avatar Aug 10 '24 14:08 mercera