Create Tags Input Field in React.js — No package Required!

Photo by RoonZ nl on Unsplash

Create Tags Input Field in React.js — No package Required!

Do you want to collect multiple values using a single input field in your application? Then what you need is a Tags Input field or any name you can call it such as keywords input.

Just as its name suggests, the Tags or Keywords Input field allows you to take multiple values from users without duplicating the input field.

Here is a visual demo of a typical Tags Input field when creating a gig on Fiverr:

Fiverr Tags demo

Aside from Fiverr, many other websites use such tags' input components to achieve similar goals.

In this article, you will learn how you can build and customize a tags input field in React.js without installing any third-party package.

Here is what we want to do:

  • Create an array of strings to hold our tags and keep track of it

  • Keep track of the user input

  • When the user hits the Enter button, the input value should be stored or updated in the array

  • When the user clicks the "x" icon, it should delete the added tag

  • We should set the maximum number of tags we need users to add to the array.

To make our code neater, we'll be building a custom hook to handle the logic.

Let's get started!

Step 0: Bootstrap a React Project

Kindly skip this part if you already have a project.

To bootstrap a React application, I will be using the Next.js framework. You can use any framework of your choice.

If you would also like to use Nextjs, run the command below to get started:

yarn add next@latest

Follow the prompts as you wish, but I will be using Tailwind CSS, Typescript, and the App router.

Once it's done installing, cd into the project folder, remove the unwanted code in the index folder and global.css, and then finally run your project.

Step 1: Create the useTag hook

Create a new folder called hooks, inside the folder, create and name it useTag.tsx.

This hook will take care of issues 1, 4, and 5 listed above.

import React, { useState } from "react";

const useTagInput = (maxTags = 5) => {
  // Keep track of the tags array.

  const [tags, setTags] = useState<string[]>([]);

  // Function to handle adding the tag to the array

  const handleAddTag = (newTag: string) => {
    if (newTag && !tags.includes(newTag) && tags.length < maxTags) {
      setTags([...tags, newTag]);
    }
  };

  // Function to remove tag from array
  const handleRemoveTag = (tag: string) =>
    setTags(tags.filter((t) => t !== tag));

  // Return tags and functions from the hook

  return { tags, handleAddTag, handleRemoveTag };
};

export default useTagInput;

That's everything we need for our custom hook. As you can see it's pretty simple.

  • We are passing maxTags as a parameter to enforce the maximum values users can add to the array. And we set the default to 5.

  • In the handleAddTag function, we are expecting a value (the user input) and before we add the value to the array, we check if it doesn't exist in the array and if the items in the array are still less than the maximum number of tags we allow.

  • In the handleRemoveTag, we are also expecting a value (added tag), and once we get the value, we simply filter (delete) it out of the array.

Now, let's move on to the UI where we will be making use of this hook.

Step 2: Build a TagField component

In your project structure, you should have a components folder. Inside that folder, create a new file and call it TagField.tsx

We only need to customize our input here and lift props so that we can use it anywhere in our application.

Here is my code:

import { useState, ChangeEvent } from "react";

interface iTag {
  tags: string[];
  addTag: (tag: string) => void;
  removeTag: (tag: string) => void;
  maxTags: number;
}

export const TagField = ({ tags, addTag, removeTag, maxTags }: iTag) => {
  // track the use input

  const [userInput, setUserInput] = useState<string>(" ");

  // Handle input onChange

  const handleInputChange = (e: ChangeEvent<HTMLInputElement>) => {
    setUserInput(e.target.value);
  };

  // handle Enter key press

  const handleKeyPress = (e: React.KeyboardEvent<HTMLInputElement>) => {
    if (e.key === "Enter") {
      e.preventDefault(); // Prevent form submission or new line creation

      if (
        userInput.trim() !== "" &&
        userInput.length <= 12 &&
        tags.length < maxTags
      ) {
        addTag(userInput);
        setUserInput(""); // Clear the input after adding a tag
      }
    }
  };

  return (
    <div className="flex flex-col w-[300px] md:w-[400px]">
      <input
        name="keyword_tags"
        type="text"
        placeholder={
          tags.length < maxTags
            ? "Add a tag"
            : `You can only enter max. of ${maxTags} tags`
        }
        className="w-full border border-gray-300 rounded-md px-4 py-2"
        onKeyDown={handleKeyPress}
        onChange={handleInputChange}
        value={userInput}
        disabled={tags.length === maxTags}
      />

      {/* ===== Render the tags here ===== */}

      <div className="flex flex-row flex-wrap gap-3 mt-4">
        {tags.map((tag: string, index: number) => (
          <span
            key={`${index}-${tag}`}
            className="inline-flex items-start justify-start px-3 py-2 rounded-[32px] text-sm shadow-sm font-medium bg-blue-100 text-blue-800 mr-2"
          >
            {tag}
            <button
              className="ml-2 hover:text-blue-500"
              onClick={() => removeTag(tag)}
              title={`Remove ${tag}`}
            >
              &times;
            </button>
          </span>
        ))}
      </div>
    </div>
  );
};

That's everything we need for the TagField component. But here is what is happening in the component;

  • We are lifting 4 props: maxTags, addTag, removeTag, and tags. We will pass values from our hook to these props whenever we want to use the TagField component.

  • The handleInputChange function simply listens to what the user is typing.

  • The handleKeyPress function will be invoked whenever the user presses "Enter". But to ensure we are adding a valid input, we are checking if the user input is not empty, if the user input is not more than 12 characters, and if the tags array is not up to our maximum tags.

  • Then finally we return a tailwind-styled input to accept the values.

  • Under the inputs, we rendered the tags and styled them with tailwind. Note that on the rendered tag, we put a delete icon (x), when clicked, it will invoke the removeTag function.

Okay. We've done a lot. But just one last step and we're good to go.

Step 3: Use the TagField component

The moment of truth is here! Let's see what we've been building. Go to the page folder, and create a new file called FormPage.tsx, and put this code:

"use client";

// import the hook

// import the TagField

const FormPage = () => {
  //define the MaxTags

  const MAX_TAGS = 5;

  //Retrieve all the returned items from the hook

  const { tags, handleAddTag, handleRemoveTag } = useTags(MAX_TAGS); // pass the maximum tags

  // Handle form submission

  const handleSubmit = () => {
    // Send tags to the backend
    console.log(tags);
  };

  return (
    <section className="h-screen w-screen flex justify-center gap-y-4">
      <form>
        <TagField
          tags={tags}
          addTag={handleAddTag}
          removeTag={handleRemoveTag}
          maxTags={MAX_TAGS}
        />

        <button
          onClick={handleSubmit}
          className="bg-blue-600 text-white outline-none border-none"
        >
          Submit Tags
        </button>
      </form>
    </section>
  );
};

export default FormPage;

Then go to the App.tsx and return only this component there. You should have something like:

// import FormPage

const App = () => {
    return <FormPage />
}

export default App

That's all and your app should be working perfectly.

Here is a demo of what we just built:

Tags Input in Reactjs

Note that you can further customize this design to suit your need or project design system but the hooks and functionality here will always work for most of the Tag Input fields you need.

Feel free to comment or contact me for any questions and I'll be happy to assist.

Thanks for reading!