React and Chips

React and Chips

One of the major Skill of Frontend Engineer is to create and develop high performance and smooth 'everyday' UI elements for the users. One of such Element is "Chips".

We have encountered this element many a time while interacting and visiting most of the common websites and if not, you may have felt a need of chips at least once.

In this blog, we are going to build a similar Chips Input Feature which is fast, smooth and easy to create on your own which has a Suggestion List also.

Pre-requisite: No External Libraries Used Basic CSS and useState, useRef,useEffect Hooks

Lets Start,

First, we'll create a state list of suggestions as well as chips;

const [chips, setChips] = useState([]);
const [suggestions, setSuggestions] = useState([
    "John Doe",
    "Anupam Mittal",
    "Shushil Gupta",
    "Mihir Dev"
  ]);

Then, lets create a basic layout of the UI elements:

 <div className="chipsContainer">
      <ul >
            <li className="chipPill" >
              ChipItem <strong>X</strong>
            </li>
        <li className="userInput">
          <input></input>
            <div className="suggestions">
                  <p>
                   Suggestion
                  </p>
            </div>
        </li>
      </ul>
    </div>

Once the UI elements are placed correctly, we need to identify the UI elements that needs to be reacting on user actions, so that we can define the state and change state with respect to actions and thereby updating UI.

User Suggestions: The suggestions list is the first thing, we only need to show suggestions when the user clicks or focuses on the input box.

Therefore, we can define a state, and conditionally render the suggestion list based on focus state, for this, we can use the OnFocus and OnBlur props of input tag, like so.

//InputHandler
const handleUserInput = (event) => {
    setUserInput(event.target.value);
  };

const [focus, setFocus] = useState(false);
const [userInput, setUserInput] = useState("");
 <input
     value={userInput}
      onChange={handleUserInput}
      onFocus={() => setFocus(true)}
      onBlur={() => setFocus(false)}>
</input>

and handle state as:


      ......
      {focus ? (
            <div className="suggestions">
                  <p className="suggPill">
                   Suggestion
                  </p>
            </div>
          ) : (
            <></>
          )}

Now, we need to define and use the state list of suggestions as well as chips for that we need to handler functions:

addToTags: Which will remove the item from the suggestion and add to chips tag

removeTags: Which will remove the item from chips list and add to the suggestion list

const addToTags = (sugg) => {
    // chips.push(sugg);
    setChips([...chips, sugg]);
    setSuggestions((prevState) => prevState.filter((item) => item !== sugg));
    setUserInput("");
  };

  const removeTags = (tag) => {
    setChips((prevState) => prevState.filter((item) => item !== tag));
    setSuggestions([...suggestions, tag]);
  };

Now, We need to tie these functions to their respective element.

<li className="chipPill" onClick={() => removeTags(chip)}>

<p className="suggPill" onMouseDown={(e) => {e.preventDefault(); addToTags(sugg); }} >

Please note, here MouseDown event handler is used since the OnClick event would fire the the state change of our input field and creates a unwanted re-render thereby add to tags function is not called.

Then,

{chips.map((chip) => {
          return (
            <li className="chipPill" onClick={() => removeTags(chip)}>
              {chip} <strong>X</strong>
            </li>
          );
        })}
{suggestions.map((sugg) => {
                return (
                  <p
                    className="suggPill"
                    onMouseDown={(e) => {
                      e.preventDefault();
                      addToTags(sugg);
                    }}
                  >
                    {sugg}
                  </p>
                );
              })}

Now, we want to make a additional feature where, user when the Input field is empty and user hits backspace, the last element should be highlighted and on the second backspace, it should be removed from chips and added to suggestions:

For this, First we need to capture the KeyPress on input like:

<input .... onKeyDown={handleBackSpace} />

Handling the key events:

  const handleBackSpace = (event) => {
    if (userInput.length === 0 && event.keyCode === 8) {
      handleDelete();
    }
  };

Then, handleDelete() is a function which will in turn do the state change or the class change of ourchips``` container and highlight the last element using CSS.

For this Purpose, useRef hook comes into picture, which essentially do nothing but gives the HTML element back to the React variable and thereby allows us to manipulate the className of the DOM element which has ref attached to it.

The useRef hook:

const chipRef = useRef();
....
<ul className="" ref={chipRef}>.....</ul>

The handleDelete():

let count = 0;
  const handleDelete = () => {
    count += 1;
    const chipS = chipRef.current;

    if (count === 1) {
      chipS.className = chipS.className + " chips";
    } else if (count === 2) {
      let lastChip = chips.slice(-1)[0];

      removeTags(lastChip);
      chipS.className = "";
      count = 0;
    } else {
      chipS.className = "";
    }
  };

The CSS:

.chips .chipPill:nth-last-child(2) {
  color: #ffffff;
  background-color: #bcbcbc;
}

Thats It, I hope you enjoyed it. May the code be with you.