Doğacan Bilgili A software developer who is also into 3D-modeling and animation.

Building a tag input field component for React

5 min read 1473

React Logo

Ever wonder how to build a tag input field component for your React app? Here’s how.

First, let’s start with a visual representation of the markup that we are going to implement. We need a container wrapping both tags and an input field. As we add more tags, the input field is forced further to the right, which gives the feeling of adding the tags into the input field.

Tags to the Right of the Input Field

A pseudo markup corresponding to the visualization above would be something like the following.

<div className="container">
  {tags.map((tag) => <div>{tag}</div>)}
  <input/>
</div>

At this point, it is clear that we need a state variable to store the entered tags, as well as another state variable to control the input field. input and tags states will be empty string and empty array by default respectively.

const [input, setInput] = useState('');
const [tags, setTags] = useState([]);

The next thing we need is to listen to changes on the input field so that we can set the input state and create a logic to populate the tags state when a certain key is pressed. Let’s assume we want to separate the tags with comma key, so when that key is pressed, we will push the content of input state as a new value to tags array.

To do that, we need to use onChange and onKeyDown events. So now our markup changes a little bit with the new attributes added:

<div className="container">
  {tags.map((tag) => <div className="tag">{tag}</div>)}
  <input
    value={input}
    placeholder="Enter a tag"
    onKeyDown={onKeyDown}
    onChange={onChange}
  />
</div>

Types of input events

onChange

This event is required only for listening to the changes on the input field and updates the input state variable.

const onChange = (e) => {
  const { value } = e.target;
  setInput(value);
};

onKeyDown

Unlike onChange event, onKeyDown event exposes the key property, which tells us which keyboard key is pressed. Because we want to separate the tags with a comma, in an if statement, we need to check whether key is equal to ,.

The second check we have in the if statement is to ensure that the input state variable is not empty after the whitespace from both sides of the string is removed by trim() method.

We made a custom demo for .
No really. Click here to check it out.

The last check we need is to make sure that the tag does not exist already. Because we store tags in tags state variable, we simply check whether that array contains the same trimmedInput. If not, we can push this new tag into the tags array.

Once we are in the if statement, we need to prevent the default behaviour by calling e.preventDefault() so that the comma character does not appear in the input field even though it is registered. Next, push the tag into tags array by using setTags function. Finally, we clear the input field by passing and empty string to setInput function.

const onKeyDown = (e) => {
  const { key } = e;
  const trimmedInput = input.trim();

  if (key === ',' && trimmedInput.length && !tags.includes(trimmedInput)) {
    e.preventDefault();
    setTags(prevState => [...prevState, trimmedInput]);
    setInput('');
  }
};

Now if you enter a text and hit the comma key, you’ll see the tag rendered on top of the input field and that the input field is cleared.

Tag Rendered on Input Field

We also need to be able to delete the tags when the backspace key is pressed, for which there are two different approaches: Either delete the tag completely at once or delete the tag and return the tag content to the input field so that the user can edit the tag and submit again. I will show the implementation of the second approach.

Tag Backspaced Example

Because we need to listen for a backspace key press, we are going to use the onKeyDown event again.

This time, key should be equal to "Backspace". We also need to check that the input field is empty and tags array is not empty.

Once these conditions hold, we need to remove the last item in the tags array and put that last element into the input field.

To do so, I am simply cloning the tags state variable by spreading it into a new array and then assigning it to a new variable called tagsCopy. The reason creating a clone of the array is not to mutate the state variable.

Then I call the pop() method on that variable to get the last element in the array and to also remove it from the array. This popped value is now stored in a variable called poppedTag.

We use setTags to update the tags state with the tagsCopy array, which is the array without the last tag. And now, we need that popped tag value in the input field, which is why we call setInput(poppedTag) to update the input state variable with the last tag value.

const onKeyDown = (e) => {
  const { key } = e;
  const trimmedInput = input.trim();

  if (key === ',' && trimmedInput.length && !tags.includes(trimmedInput)) {
    e.preventDefault();
    setTags(prevState => [...prevState, trimmedInput]);
    setInput('');
  }

  if (key === "Backspace" && !input.length && tags.length) {
    e.preventDefault();
    const tagsCopy = [...tags];
    const poppedTag = tagsCopy.pop();

    setTags(tagsCopy);
    setInput(poppedTag);
  }
};

This works just fine, but there is a catch. When you hit the backspace key and have the last tag in the input field, you might want to press and hold the backspace key to delete that tag completely — but with the current implementation, you’ll end up quickly deleting one tag after another if you don’t release the key quickly.

Deleting Tags Quickly

The behaviour I would expect is to delete the characters in the input field, then stop without removing the next tag unless the user releases the key and hits backspace again.

The solution to that problem is trivial. Along with onKeyDown event, we also need to use onKeyUp event to keep track of whether a key press is released or not. That way, we can detect if the user was holding down the backspace key to clear the input field.

First, we need a new boolean state variable to toggle both when a key is pressed and released.

const [isKeyReleased, setIsKeyReleased] = useState(false);

Set isKeyReleased to false in the onKeyDown function and to true in onKeyUp.

Then we need to use isKeyReleased as a condition for the if statement of the tag deletion logic. That way, we only delete the characters in the input field when the backspace key is pressed and held.

Pressed and Held Delete

Below are the final versions of both functions.

const onKeyDown = (e) => {
  const { key } = e;
  const trimmedInput = input.trim();

  if (key === ',' && trimmedInput.length && !tags.includes(trimmedInput)) {
    e.preventDefault();
    setTags(prevState => [...prevState, trimmedInput]);
    setInput('');
  }

  if (key === "Backspace" && !input.length && tags.length && isKeyReleased) {
    const tagsCopy = [...tags];
    const poppedTag = tagsCopy.pop();
    e.preventDefault();
    setTags(tagsCopy);
    setInput(poppedTag);
  }

  setIsKeyReleased(false);
};

const onKeyUp = () => {
  setIsKeyReleased(true);
}

Don’t forget to update the HTML input tag with onKeyUp handler.

<input
  value={input}
  placeholder="Enter a tag"
  onKeyDown={onKeyDown}
  onKeyUp={onKeyUp}
  onChange={onChange}
/>

Deleting a specific tag

Being able to delete a specific tag is another required feature in order to create a good user experience. In order to implement this, we need to slightly change the tag markup by only adding an HTML button tag.

The onClick event handler of the button calls a function named deleteTag, where index of a tag is passed as an argument.

{tags.map((tag, index) => (
  <div className="tag">
    {tag}
    <button onClick={() => deleteTag(index)}>x</button>
  </div>
))}

The deleteTag function filters out the tags by their index values. Because we passed the index of the tag that we want to delete, we pick all the tags that don’t have that same index position in the array.

const deleteTag = (index) => {
  setTags(prevState => prevState.filter((tag, i) => i !== index))
}

Delete Tags With Box

At this point, we have a fully functional tag input field, but it obviously does not look good. I will not get into the details of styling, but below you can find an example CSS file so that it looks like this:

Tag Auto Add

.container {
  display: flex;
  overflow: scroll;
  width: 100%;
  max-width: 100%;
  padding-left: 14px;
  border: 1px grey solid;
  border-radius: 5px;
  color: black
}

.container input {
  width: 100%;
  min-width: 50%;
  border: none;
  border-radius: 5px;
  padding: 14px;
  padding-left: 14px;
}

.tag {
  display: flex;
  align-items: center;
  margin: 7px 0;
  margin-right: 10px;
  padding: 0 10px;
  padding-right: 5px;
  border: 1px solid orange;
  border-radius: 5px;
  background-color: orange;
  white-space: nowrap;
  color: white;
}

.tag button {
  display: flex;
  padding: 6px;
  border: none;
  background-color: unset;
  cursor: pointer;
  color: white;
}

I hope you’ve found this tutorial on building a tag input field component in React helpful. Thanks for reading.

Full visibility into production React apps

Debugging React applications can be difficult, especially when users experience issues that are difficult to reproduce. If you’re interested in monitoring and tracking Redux state, automatically surfacing JavaScript errors, and tracking slow network requests and component load time, try LogRocket.

LogRocket is like a DVR for web apps, recording literally everything that happens on your React app. Instead of guessing why problems happen, you can aggregate and report on what state your application was in when an issue occurred. LogRocket also monitors your app's performance, reporting with metrics like client CPU load, client memory usage, and more.

The LogRocket Redux middleware package adds an extra layer of visibility into your user sessions. LogRocket logs all actions and state from your Redux stores.

Modernize how you debug your React apps — .

Doğacan Bilgili A software developer who is also into 3D-modeling and animation.

One Reply to “Building a tag input field component for React”

Leave a Reply