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.
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.
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.
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>
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.
Want to conquer your next JavaScript interview? Download my FREE PDF - Pass Your JS Interview with Confidence and start preparing for success today!
📚 Source code of what we've done