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:
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 create next-app
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}`}
>
×
</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
, andtags
. 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:
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!
Top comments (4)
Great article, will implement it in my projects
Thank you @the-arcade-01
Nice article
Thanks for checking 🙏🙏