DEV Community

Cover image for Building a RichText Editor with TipTap in React (with Mentions)
Abdelraman Ahmed
Abdelraman Ahmed

Posted on

Building a RichText Editor with TipTap in React (with Mentions)

If you want to enhance your React app with a powerful, customizable RichText editor, TipTap is an excellent choice. This tutorial will guide you through integrating TipTap into your project and adding a mentions feature for a dynamic user experience.

What You’ll Build

By the end of this tutorial, you’ll have:

  1. A fully functional RichText editor built with TipTap.
  2. Support for mentions triggered by @, complete with dynamic suggestion lists.
  3. Insight into resolving edge cases like placeholder visibility and cursor preservation.

For more on TipTap, visit the official documentation or explore their GitHub repository.

Step 1: Install Dependencies
Before diving in, install the required libraries:

npm install @tiptap/react @tiptap/starter-kit @tiptap/extension-mention
Enter fullscreen mode Exit fullscreen mode

Step 2: Create a Basic RichText Editor

Start by creating a RichTextEditor component. Here’s a simple implementation:

import { useEditor, EditorContent } from '@tiptap/react';  

import StarterKit from '@tiptap/starter-kit';  

export const RichTextEditor = ({ content, onChange }) => {  
  const editor = useEditor({  
    extensions: [StarterKit],  
    content: content,  
    onUpdate: ({ editor }) => {  
      onChange(editor.getHTML());  
    },  
  });  
  return <EditorContent editor={editor} />;  
};
Enter fullscreen mode Exit fullscreen mode

Step 3: Add Mentions

Mentions enhance user interactivity, especially in chat or collaborative applications. To implement them:

  1. Install and Configure the Mention Extension

Modify the RichTextEditor component to include the Mention extension:

import Mention from '@tiptap/extension-mention';  

export const RichTextEditor = ({ content, onChange, mentions }) => {  
  const editor = useEditor({  
    extensions: [  
      StarterKit,  
      Mention.configure({  
        HTMLAttributes: { class: 'mention' },  
        suggestion: {  
          items: ({ query }) =>  
            mentions.filter(item => item.display.toLowerCase().includes(query.toLowerCase())).slice(0, 5),  
          render: () => {  
            let component;  
            let popup;  
            return {  
              onStart: (props) => {  
                popup = document.createElement('div');  
                popup.className = 'mention-popup';  
                document.body.appendChild(popup);  
                component = {  
                  updateProps: () => {  
                    popup.innerHTML = `  
                      <div class="items">  
                        ${props.items.map(item => `  
                          <button class="item ${item.selected ? 'is-selected' : ''}">${item.display}</button>  
                        `).join('')}  
                      </div>  
                    `;  
                  },  
                  destroy: () => popup.remove(),  
                };  
                popup.addEventListener('click', (e) => {  
                  const button = e.target.closest('button');  
                  if (button) {  
                    const index = Array.from(popup.querySelectorAll('.item')).indexOf(button);  
                    props.command({ id: props.items[index].id, label: props.items[index].display });  
                  }  
                });  
                component.updateProps();  
              },  
              onExit: () => component?.destroy(),  
            };  
          },  
        },  
      }),  
    ],  
    content,  
    onUpdate: ({ editor }) => onChange(editor.getHTML()),  
  });  
  return <EditorContent editor={editor} />;  
};
Enter fullscreen mode Exit fullscreen mode

Step 4: Style the Mentions Popup

Mentions should be visually distinct. Add the following styles to enhance usability:

.mention-popup {  
  background: white;  
  border-radius: 8px;  
  box-shadow: 0px 2px 8px rgba(0, 0, 0, 0.1);  
  padding: 8px;  
  position: absolute;  
  z-index: 1000;  
}  
.mention-popup .items {  
  display: flex;  
  flex-direction: column;  
}  
.mention-popup .item {  
  padding: 8px;  
  cursor: pointer;  
  border-radius: 4px;  
}  

.mention-popup .item:hover,  
.mention-popup .item.is-selected {  
  background: #f0f0f0;  
}
Enter fullscreen mode Exit fullscreen mode

Step 5: Edge Cases I Faced During Implementation

  1. Cursor Jumping: To avoid cursor jumping while typing, ensure content updates preserve the cursor’s position:
const editor = useEditor({  
  extensions: [StarterKit],  
  content,  
  onUpdate: ({ editor }) => {  
    const selection = editor.state.selection;  
    onChange(editor.getHTML());  
    editor.commands.setTextSelection(selection);  
  },  
});
Enter fullscreen mode Exit fullscreen mode
  1. Placeholder Not Visible:

Use the Placeholder extension to display a hint when the editor is empty:

import Placeholder from '@tiptap/extension-placeholder';  
const editor = useEditor({  
  extensions: [  
    StarterKit,  
    Placeholder.configure({ placeholder: 'Type something...' }),  
  ],  
});
Enter fullscreen mode Exit fullscreen mode
  1. Mention Suggestions Not Showing: Check the suggestion.items method to ensure it filters and returns the expected list.

Step 6: Integrate into Your App

Wrap the editor in a modal or form component to make it part of a larger feature, such as notifications or comments. Here’s an example:

import React from 'react';  

const NotificationForm = ({ mentions, onSubmit }) => {  
  const [content, setContent] = React.useState('');  
  return (  
    <form onSubmit={() => onSubmit(content)}>  
      <RichTextEditor content={content} onChange={setContent} mentions={mentions} />  
      <button type="submit">Send</button>  
    </form> 
  );  
};
Enter fullscreen mode Exit fullscreen mode

Conclusion

With TipTap, you can build a RichText editor that’s both powerful and user-friendly. Adding mentions enhances the interactivity of your app, making it more engaging for users.

For more, visit the official TipTap website. Did you learn something new from this article? Let me know in the comments! 🎉

Sentry blog image

How to reduce TTFB

In the past few years in the web dev world, we’ve seen a significant push towards rendering our websites on the server. Doing so is better for SEO and performs better on low-powered devices, but one thing we had to sacrifice is TTFB.

In this article, we’ll see how we can identify what makes our TTFB high so we can fix it.

Read more

Top comments (0)

Billboard image

The Next Generation Developer Platform

Coherence is the first Platform-as-a-Service you can control. Unlike "black-box" platforms that are opinionated about the infra you can deploy, Coherence is powered by CNC, the open-source IaC framework, which offers limitless customization.

Learn more

👋 Kindness is contagious

Explore a sea of insights with this enlightening post, highly esteemed within the nurturing DEV Community. Coders of all stripes are invited to participate and contribute to our shared knowledge.

Expressing gratitude with a simple "thank you" can make a big impact. Leave your thanks in the comments!

On DEV, exchanging ideas smooths our way and strengthens our community bonds. Found this useful? A quick note of thanks to the author can mean a lot.

Okay