React Mentions - Building React Autocomplete Input for Comments
React Mentions - Building React Autocomplete Input for Comments

In this post I want to show you how to implement mentioning functionality inside comments if you implement project with React.

Finished project

So what does mention feature means? Typically you can see this feature for example on Twitter or on Youtube, you have a textarea where you can type something and at some point you write @ and you get a list of users that you can mention. So the mentioned user will see the mention and write a reply.

Actually it might look like a small feature but it is not. This is why for your understanding here I opened a codepen with the example of implementation.

Codepen

There is lots of code because of popup positioning. We can implement it on our own but it will take quite significant amount of time and we will for sure get lots of bugs with positioning.

This is why I highly recommend to use a library instead. There is a React Mentions library that you can use instead. It is extremely flexible and configurable.

Installation

The first step is to create React project. So I already generated a create-react-app. Now we must install this library

yarn add react-mentions

Now let's create our component Mentions.

const Mentions = () => {
  return (
    <div>
    Mentions
    </div>
  );
};

export default Mentions;

Now let's add Mention components to our markup

import {MentionsInput, Mention} from 'react-mentions'
const Mentions = () => {
  return (
    <div>
      <MentionsInput>
        <Mention/>
      </MentionsInput>
    </div>
  );
};

export default Mentions;

This will just render an input without any information. Because actually we are missing quite a lot of stuff and first of all we are missing styling.

This is why here is default styles for react-mentions that we must inject.

// src/defaultMentionStyle.js
export default {
  backgroundColor: "#cee4e5",
};
// src/defaultStyle.js
export default {
  control: {
    backgroundColor: "#fff",
    fontSize: 14,
    fontWeight: "normal",
  },

  "&multiLine": {
    control: {
      fontFamily: "monospace",
      minHeight: 63,
    },
    highlighter: {
      padding: 9,
      border: "1px solid transparent",
    },
    input: {
      padding: 9,
      border: "1px solid silver",
    },
  },

  "&singleLine": {
    display: "inline-block",
    width: 180,

    highlighter: {
      padding: 1,
      border: "2px inset transparent",
    },
    input: {
      padding: 1,
      border: "2px inset",
    },
  },

  suggestions: {
    list: {
      backgroundColor: "white",
      border: "1px solid rgba(0,0,0,0.15)",
      fontSize: 14,
    },
    item: {
      padding: "5px 15px",
      borderBottom: "1px solid rgba(0,0,0,0.15)",
      "&focused": {
        backgroundColor: "#cee4e5",
      },
    },
  },
};

This is the styling for mentions container and mention popup. Now we need to use it inside our component.

import defaultMentionStyle from './defaultMentionStyle'
import defaultStyle from './defaultStyle'
const Mentions = () => {
  return (
    <div>
      <MentionsInput style={defaultStyle}>
        <Mention style={defaultMentionStyle} />
      </MentionsInput>
    </div>
  );
};

export default Mentions;

Providing data

Now we must provide data to our mentions component.

...

const users = [
  {
    id: "jack",
    display: "Jack",
  },
  {
    id: "john",
    display: "john",
  },
];

const Mentions = () => {
  return (
    <div>
      <MentionsInput style={defaultStyle}>
        <Mention style={defaultMentionStyle} data={users} />
      </MentionsInput>
    </div>
  );
};

export default Mentions;

We created an array of objects with id and display this is the format which react-mentions want to get. This data we must provide inside Mention component in prop data.

And last step is to set to the state the value of Mentions.

const Mentions = () => {
  const [value, setValue] = useState("");
  const onChange = (e) => {
    console.log("onChange", e);
    setValue(e.target.value);
  };
  return (
    <div>
      <MentionsInput style={defaultStyle} value={value} onChange={onChange}>
        <Mention style={defaultMentionStyle} data={users} />
      </MentionsInput>
    </div>
  );
};

We registered our new state value, provided it inside and added onChange event which will update our state.

Initial implementation

This is the full setup of mentioning functionality.

It is important to mention what is set in our state.

Some text @[John](john)

As you can see when we select a user we get a specific notation inside our string. In this way we can parse later this structure, get the ID of the user on backend and notify him about mentioning.

Single line

As you can already see this library covers our need. But what can we do if we don't need a textarea but an input? This is super easy to implement, we just need to put an additional property singleLine.

<MentionsInput ... singleLine>

Single line

Here we get an input which works exactly like textarea with mentioning our of the box.

Asynchronous calls

Another important question here is how we can make asynchronous calls to get data from API. Typically we won't have all our users on client so we need this mechanic. And it is possible. We must create an asynchronous function with a callback call.

const fetchUsers = (query, callback) => {
  if (!query) return;

  setTimeout(() => {
    const filteredUsers = users.filter((user) =>
      user.display.toLowerCase().includes(query)
    );
    callback(filteredUsers);
  }, 2000);
};

This function gets searched query and a callback. We use query to make an API call to database and filter our users an callback to notify our component that we are ready with data. Here we used setTimeout to simulate asynchronous call to our API.

Now we must provide this function inside our component.

<Mention data={fetchUsers} style={defaultMentionStyle} />

So instead of plain data we provide a function inside. As you can see in browser we get our data in 2 seconds after asynchronous load.

Parsing string

And the last question is how you can parse the result from react-mentions to get a list of mentioned users on the backend. And typically to parse string we use regular expressions. It is not a topic of this post so I just show you how you can get users from the string here.

"gegwegwe @[Jack](jack)  gdsgdsg @[foo](john).  ".match(/[^(]+(?=\))/g)

We have here a string and by using match we will get our users back so we can notify them on our backend.

Also if you are interested how we implemented custom hooks inside React make sure to check this post also.

📚 Source code of what we've done