Build a Custom Draft.js Comment Box with Length Limit, Mentions, and Links
This tutorial introduces Draft.js core concepts—EditorState, Entity, SelectionState, and CompositeDecorator—and walks through building a custom comment box in React that enforces a 200‑character limit, highlights @mentions, and supports link insertion using immutable state updates.
Draft.js Overview
Draft.js is a React rich‑text editor framework that provides APIs such as EditorState, Entity, SelectionState, and CompositeDecorator, allowing developers to build custom editors.
EditorState
EditorState is the top‑level immutable state object representing the whole editor, including ContentState, SelectionState, decorators, undo/redo stack, and change type.
Current content (ContentState)
Current selection (SelectionState)
Decorator
Undo/redo stack
EditorChangeType
Because Draft.js works with immutable data, any modification creates a new EditorState instance.
Entity
Entity describes a piece of text with metadata, enabling features such as links, mentions, and embedded content.
{
type: 'string',
// entity type, e.g. 'LINK', 'TOKEN', 'PHOTO', 'IMAGE'
mutability: 'MUTABLE' | 'IMMUTABLE' | 'SEGMENTED',
// mutability controls how the text can be edited
data: 'object',
// custom metadata
}Mutability values:
IMMUTABLE – the whole entity is removed or cannot be edited.
MUTABLE – the text inside can be edited (e.g., a link).
SEGMENTED – similar to immutable but allows partial deletion.
SelectionState
SelectionState represents the range of text selected in the editor, defined by an anchor (start) and a focus (end). The relative positions determine the direction of selection.
CompositeDecorator
CompositeDecorator scans ContentBlocks, finds matches based on a strategy, and renders them with a custom React component.
Implementing a Comment Box
Requirements: limit input to 200 characters, highlight @mentions, and insert links.
Length Restriction
Use handleBeforeInput and handlePastedText to block input when the current length plus the new characters would exceed the limit. The functions consider the length of selected text that will be replaced.
const MAX_LENGTH = 200;
// handleBeforeInput implementation …
// handlePastedText implementation …Store the current length in state inside handleEditorChange to display a “current/maximum” counter.
@Mention Highlighting
Create a CompositeDecorator with a regex /@[\w]+/g that wraps matched text in a span with a special class.
const HANDLE_REGEX = /@[\w]+/g;
const compositeDecorator = new CompositeDecorator([
{
strategy: (contentBlock, callback) => {
const text = contentBlock.getText();
let matchArr, start;
while ((matchArr = HANDLE_REGEX.exec(text)) !== null) {
start = matchArr.index;
callback(start, start + matchArr[0].length);
}
},
component: props => <span className="mention">{props.children}</span>
}
]);Link Insertion
Use Entity with type LINK (immutable) and the Modifier API to insert or replace text, then force the selection to the end of the inserted link.
const insertEntity = (entityData) => {
let contentState = editorState.getCurrentContent();
contentState = contentState.createEntity('LINK', 'IMMUTABLE', entityData);
const entityKey = contentState.getLastCreatedEntityKey();
let selection = editorState.getSelection();
if (selection.isCollapsed()) {
contentState = Modifier.insertText(contentState, selection, entityData.name + ' ', undefined, entityKey);
} else {
contentState = Modifier.replaceText(contentState, selection, entityData.name + ' ', undefined, entityKey);
}
// move cursor to the end of the inserted link
let end;
contentState.getFirstBlock().findEntityRanges(
character => character.getEntity() === entityKey,
(_, _end) => { end = _end; }
);
let newEditorState = EditorState.set(editorState, { currentContent: contentState });
selection = selection.merge({ anchorOffset: end, focusOffset: end });
newEditorState = EditorState.forceSelection(newEditorState, selection);
handleEditorChange(newEditorState);
};After implementing the three features, the editor provides a functional comment box with length feedback, @mention highlighting, and clickable links.
Qingyun Technology Community
Official account of the Qingyun Technology Community, focusing on tech innovation, supporting developers, and sharing knowledge. Born to Learn and Share!
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
