Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
mercera opened this issue Aug 7, 2024 · 4 comments
Open
Labels
question Further information is requested

Comments

@mercera
Copy link

mercera commented Aug 7, 2024

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. :)

@ellunium
Copy link
Contributor

ellunium commented Aug 7, 2024

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
    />
  );
}

@mercera
Copy link
Author

mercera commented Aug 8, 2024

Thanks 👍. Will try this out.

@i-like-robots
Copy link
Owner

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?

@mercera
Copy link
Author

mercera commented Aug 10, 2024

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.

@i-like-robots i-like-robots added the question Further information is requested label Oct 4, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
question Further information is requested
Projects
None yet
Development

No branches or pull requests

3 participants