Building Editors in React Applications

What to select between DraftJs, SlateJs, and existing WSYWIG editor Froala?

We had to create editors for two different use cases almost polar opposite in number of features they have. One was a simple editor to post status and the other was document editor for templating business agreements.

Status Post Editor ✍️

At Rippling, we use React for creating almost all our user interfaces. We wanted to create a simple interface where people could share their status. We also wanted ability to add rich text and mention other people in our status post editor. So we decided to go with building an editor component of our own instead of using a textarea.

We explored DraftJs and SlateJs. Both are very well maintained and leading projects in React Ecosystem.

DraftJS vs SlateJS

  1. Plugin System:
    You will have to use DraftJs plugins along with DraftJs to be able to create and re-use plugins. There is no plugin system in DraftJs itself. On the other hand, SlateJs has first class plugins system.
  2. Nested Structures:
    There’s no good way to implement tables right now in DraftJs.
    DraftJs’s content model is flat. That’s a restriction that comes with it’s own pros and cons. Nested structures are harder to optimise and reason about, so this makes maintaining DraftJs easier. However, this restriction means that implementing a table layout is hard to do without nesting editors. Nested editors, although doable and probably the best way to implement table functionality in DraftJs right now, comes with a bunch of extra complexity, try to stay away from this if you can.
    There is an ongoing effort to implement a tree structure in DraftJs but we have no idea as to when this feature will actually land.

    — you can read more here

    Slate Js has schema less content model so you can create nested structures like tables, nested lists without nesting editors.

  3. Immutable data structures
    Both use immutable data structures to store editor state.
  4. Serializing to HTML
    Immutable data Structure is great for managing editor state. But your editor needs to consume HTML and/or spit HTML. Since HTML is used by other systems, interfaces. So serializing and de-serializing to HTML is important.
    Slate Js has inbuilt plugins and doc to help with this. However, with DraftJs you would have to rely on third party lib or write your own serializer/de-serializer.

For creating a simple post editor you can use either Slate or DraftJs + Plugins. We went ahead with DraftJs + Plugins-Editor.

Below is the gist of how you can start with DraftJs plugins to create a post editor and get html out on submit.

class PostEditor extends Component {
constructor(props) {
super(props);
this.mentionPlugin = createMentionPlugin({
entityMutability: "IMMUTABLE",
mentionComponent: MentionComponent // since we want to remove the entire name at once.
});
this.state = {
editorState: EditorState.createEmpty(),
suggestions: this.props.mentions
};
}
reset = () => {
this.setState({
editorState: EditorState.createEmpty()
});
};
onChange = editorState => {
this.setState({
editorState
});
};
onSearchChange = ({ value }) => {
this.setState({
suggestions: defaultSuggestionsFilter(value, this.props.mentions)
});
};
keyBindingFn = e => {
// retrun custom commands on keyPress if required
return getDefaultKeyBinding(e);
};
toHtml = () => {
const contentState = this.state.editorState.getCurrentContent();
const options = {
// eslint-disable-next-line consistent-return
entityStyleFn: entity => {
const entityType = entity.get("type").toLowerCase();
if (entityType === "mention") {
const data = entity.getData();
return {
element: "span",
attributes: {
"data-mention-id": _.get(data, "mention.id"),
class: "mention_class"
},
style: {
// Put styles here...
}
};
}
}
};
return stateToHTML(contentState, options);
};
handleKeyCommand = command => {
// handle custom command here;
const newState = RichUtils.handleKeyCommand(
this.state.editorState,
command
);
if (newState) {
this.onChange(newState);
return cmdState.handled;
}
return cmdState.notHandled;
};
render() {
const { MentionSuggestions } = this.mentionPlugin;
const plugins = [this.mentionPlugin];
const { className, style, placeholder } = this.props;
return (
<div className={`editor ${className}`} style={style}>
<Editor
editorState={this.state.editorState}
onChange={this.onChange}
plugins={plugins}
keyBindingFn={this.keyBindingFn}
handleKeyCommand={this.handleKeyCommand}
placeholder={placeholder}
ref={element => {
this.editor = element;
}}
/>
<MentionSuggestions
onSearchChange={this.onSearchChange}
suggestions={this.state.suggestions}
entryComponent={EntryComponent}
/>
</div>
);
}
}
PostEditor.propTypes = {
/**
* mentions {array} - array of names for `@`mentions to work
*/
mentions: PropTypes.arrayOf(
PropTypes.shape({
name: PropTypes.string,
id: PropTypes.string
})
),
/**
* className {string} - className applied to top most Wrapper
*/
className: PropTypes.string,
/**
* style {object} - inline style to be applied to top most Wrapper
*/
style: PropTypes.object,
/**
* placeholder {string} - placeholder to display when editor has no text
*/
placeholder: PropTypes.string
};
PostEditor.defaultProps = {
mentions: []
};
view raw PostEditor.js hosted with ❤ by GitHub

Working example in the sandbox below.

Building a document editor in React application ✍️

We had to replace our existing Summernote based editor because we were facing a lot customer issues while pasting documents, creating tables and placing images.

We wanted to create a trimmed down version of Google docs, so that our users can create document templates using the editor in our apps itself.

 

First, we tried SlateJs and using DraftJs plugins the main problem was that we would have to build the whole editor by scratch.
You can see the above trimmed down version of post editor has only one notable feature that is mention. However, building that with even with existing plugin took a lot of boilerplate code.

Essentially, these two libraries SlateJs and DraftJs give you a content editable textarea and a framework around that for you to build your own editor.

Also, Learning curve is significant with both of these frameworks. If you want to develop an editor which is not as limited as the above post editor you would need a good grasp of there API, concepts like immutability of editor state, entities, decorators etc.

Don’t believe me, check it here SlateDocs or DraftJSDocs.

Slate JS docs are way better and straight forward compared to Draft Js.

These are “Frameworks to build editors” and not editors themselves.

As any early stage startup, we do not have the luxury to spend a lot of time/resources building an entire document editor by scratch. So we decided to reuse an existing WYSIWYG editor as our base component and create a wrapper over that to add custom features.

Now, the difficult part selecting the right WYSWIG Editor.

There are a lot of editors to choose from some are paid some free. Some created using DraftJs/SlateJs and a lot of them are using jQuery/Vanilla JS. They also tend to differ in features and API.

To decide on one we first made a list of what we are exactly looking from the editor. We created a list of tests to shortlist the editor we would use based on the pain points we had with Summernote.

  1. Paste an existing document from Google docs/Microsoft Word to check how they handle HTML paste processing. This was very important to us. Since we were creating a document editor inside our app. Most of our clients would paste their existing created documents from Google docs/Microsoft Word. It was necessary that the new editor handles HTML paste in at-least an acceptable way or else we would have to again tap in the `after paste hook` and add a lot the HTML paste processing logic ourselves. So we took a document where we had nested lists, table and an image with a lot of text. After Pasting, we checked whether Text Formatting and indentation was preserved. List and Nested Lists were treated as list and nested lists. You can test it like this in the UI without even opening dev tools. Suppose you have 3 ordered lists 1, 2 and 3. and then you press enter at the end of 3 it should automatically add 4. This indicates it has properly parsed your HTML list correctly. Similarly, you can test with nested lists.
  2. It should parse the existing table correctly and have basic table/cell editing features, also it should handle tabbing in tables cells.
  3. Image insertion and editing capabilities.

One Editor stood out and met most of the above tests Froala.

Even though it was NOT an open source or free to use Editor.

It is an excellent editor which is very well maintained, has rich and extensive API with reasonably good documentation.

Also if like us if you want to build a document editor Froala has a special mode called “Document Mode”.
Check out Froala docs. They also have a React integration to get you started.

This is how you can create a wrapper around Froala using React-froala and get started.

Yes, Below example is using React hooks. ???

const EditorComponent = forwardRef(({ defaultContent }, ref) => {
// State
const [state, setState] = useReducer(
(prevState, nextState) => ({
...prevState,
...nextState
}),
{
content: defaultContent || ""
}
);
const editor = useRef({});
const initializeEditor = (e, editorRef) => {
editor.current = editorRef;
window.editor = editorRef;
};
useImperativeHandle(ref, () => ({
getContent
}));
const getContent = () => {
return editor.current.html.get();
};
const editorConfig = {
placeholder: "Edit Me",
documentReady: true,
events: {
"froalaEditor.initialized": initializeEditor
}
};
const onContentChange = model => {
this.setState({
content: model
});
};
return (
<div className="main-editor-container">
<FroalaEditor
config={editorConfig}
tag="textarea"
model={state.content}
onModelChange={onContentChange}
/>
</div>
);
});
view raw FroalaEditor.js hosted with ❤ by GitHub
Click on image to open sample Froala sandbox

Conclusion

If you have a limited use-case like chat-box, post editor, comment widget or the likes. DraftJs plugins Editor is the way to go. Most of the plugins (at least the official ones) are created I guess keeping these use-cases in mind. You will find emoji plugin, mention, link etc.

If you have a Content Management System like Medium or GitBook to make, you would ideally want to create your own editor from scratch. I would prefer SlateJs any day over DraftJs for this. You get all the good parts of DraftJs without any of it’s limitations. Read “Why SlateJs?” here.

If you want to ship your WSYWIG editor fast and you do not have resources in writing an WSYWIG editor from scratch, use an existing editor like Froala.