Make Your Own Rich Text Editor in Reactjs using draftjs

Written by devyendushekhar | Published 2019/02/01
Tech Story Tags: react | rich-text-editor | draftjs | draftjs-editor | make-rich-text-editor

TLDRvia the TL;DR App

Why is it important to know about Rich Text Editors?

You might have seen Intro section like above on instagram, facebook and on other platforms as well. They support a lot of cool features like emojis, hashtags, @ mentions and others stuff as well. And Even other types of stylings as well ( like BOLD, ITALIC, BLOCKQUOTE e.t.c ). Have you ever wondered how to implement this in your own react app? If yes, then you are at the right place as after going through this article you will be able to make your own Rich Text Editors like that. Oh, 😄 as the last line and the title suggests this article is for those who are aware of basics of React, that’s the only pre-requisite I promise. So make sure, you are comfortable with React before reading this article.

Let’s Start Making Our Own Rich Text Editor

Before actually starting to write the React code, we need React ecosystem, which means all those Webpack configurations, and configurations for babel transpiler which is an important part but doing all those things from scratch will overshadow rich text editors for which this whole article is dedicated for. Thanks, Facebook for their open source project create-react-app using which we can start making React App by writing one single command into your terminal.

So let’s make our React app using create-react-app. I will not install this package globally in my system instead I will use npx ( It will automatically install a package with that name from the npm registry for you, and invoke it. When it’s done, the installed package won’t be anywhere in your globals, so you won’t have to worry about pollution in the long-term ).

npx create-react-app myappcd myappnpm start

Boom, your react app is hosted on localhost:3000/.

You will have a directory structure like this

my-app├── README.md├── node_modules├── package.json├── .gitignore├── public│   ├── favicon.ico│   ├── index.html│   └── manifest.json└── src    ├── App.css    ├── App.js    ├── App.test.js    ├── index.css    ├── index.js    ├── logo.svg    └── serviceWorker.js

So finally, we are ready to actually code our Rich Text Editor( excited ❕ ❕ ). So what are we going to make? Check My Rich-Text-Editor. Try typing ( : , @, or any link ) you can clearly see the power of Rich Text Editors. You might have also seen some variables like ( organization.name, organization.email and many others). And as soon as you click them you get mustache syntax of handlebars. Don’t be afraid, if you are not aware of handlebars. It is not required for this article. I implemented them, for one of my projects where I needed to make an interface where each organization can design their own email templates for some action triggers.

Basics of draft-js

The basic component used in Draft-js is an Edit control. If we compare a text input to an Edit control, a Text input has a value that is a text string. In an Edit control, we need more than simple text, we need an object that holds the text, cursor position etc. Instead of just text, we use an EditorState object. There are a few key things that one must know to use the Editor control successfully:

  • The EditorState control is immutable. This means you never change it or any of its properties. Instead, if you want to change something like the text, you use a method ( onChange() to be specific ) that gives you back an entirely new EditorState which is a copy of the old EditorState and your new changes ( Remember “state” object in React, just like we used setState() method to change the state instead of directly mutating the object ) The new EditorState is then given to the edit control where you can see your changes.
  • The EditorState controls have child objects that show the actual state of what is being represented in the editor including text, cursor/selection, styling etc.

Definitions

  • EditorState: The object responsible for the current state as represented in the Editor
  • ContentState: The object responsible for the text in the Editor. Content is made up of blocks of Content.
  • SelectionState: The object responsible for the cursor and the cursor’s selection area
  • Entities: Metadata that can be added to a portion of text

These were some of the basic terminologies which you gonna see everywhere in code and draft-js docs as well.

Our Project

Let’s come back to our react-app. You can clone this project from Github for side by side reviewing, but I will recommend you to make it on your own. App.js in the project is our root component. Which looks like this

import React, { Component } from 'react';import MyEditor from './Editor'

class App extends Component {render() {return (<MyEditor/>);}}

export default App;

Now, where is that MyEditor coming from? It is coming from Editor.js our other component. Now, before making this component let’s install draft-js and draft-js-plugins-editor. Wait, you can implement your editor without the later npm library also. So why we are importing it? The answer is, it gives you a way to add a lot of plug-n-play extensions in your editor out of the box like (mention, emojis, linkify, inline-toolbar e.t.c ). This editor has been built over Editor of draft-js only. Since this library gives the editor only. For other kinds of stuff, we still require draft-js. For more details check out this.

npm install draft-js draft-js-plugins-editor --save

Okay, Now we are ready for Editor.js

import React, { Component } from 'react';import { convertToRaw, EditorState, RichUtils } from 'draft-js';import Editor from 'draft-js-plugins-editor';

Import these things.

class MyEditor extends Component {

constructor(props) {  
    _super_(props);  
    _this_.state = { editorState: EditorState.createEmpty() };  
  _this_.onChange = (editorState) => _this_.setState({editorState});  
}

handleKeyCommand = ( command, editorState ) => {

let newState;newState = RichUtils.handleKeyCommand( editorState, command );if( newState ) {this.onChange(newState);return "handled"}

return 'non-handled'

};

<EditoreditorState={ this.state.editorState }onChange={ this.onChange }handleKeyCommand={ this.handleKeyCommand }/>

}

We have initiated the empty state of the editor using the EditorState.createEmpty() method and added it into the state of MyEditor Component. So that, the component re-renders when the Editor State changes. Editor component has 3 props first one is editorState, and the next one is onChange, which shows that it is a controlled component, and the third one is handleKeyCommand which uses RichUtils which provides rich text editing for the selected state. Try typing cmd + b, cmd +i, cmd + u and cmd + z in your editor and see the magic. You can also make custom commands check here. That is what Rich utils do for us. Now, let us add some draft-js plugins and see the magic.

Mention Plugin

import the plugin and its CSS file.

import createMentionPlugin, { defaultSuggestionsFilter } from 'draft-js-mention-plugin';import 'draft-js-mention-plugin/lib/plugin.css';

create a mention.js file and feed some data into it and then export data from that file.

mention.js

const mentions = [{name: 'Matthew Russell',link: 'https://twitter.com/mrussell247',avatar: 'https://pbs.twimg.com/profile_images/517863945/mattsailing_400x400.jpg',},{name: 'Julian Krispel-Samsel',link: 'https://twitter.com/juliandoesstuff',avatar: 'https://avatars2.githubusercontent.com/u/1188186?v=3&s=400',},{name: 'Jyoti Puri',link: 'https://twitter.com/jyopur',avatar: 'https://avatars0.githubusercontent.com/u/2182307?v=3&s=400',},{name: 'Max Stoiber',link: 'https://twitter.com/mxstbr',avatar: 'https://pbs.twimg.com/profile_images/763033229993574400/6frGyDyA_400x400.jpg',},{name: 'Nik Graf',link: 'https://twitter.com/nikgraf',avatar: 'https://avatars0.githubusercontent.com/u/223045?v=3&s=400',},{name: 'Pascal Brandt',link: 'https://twitter.com/psbrandt',avatar: 'https://pbs.twimg.com/profile_images/688487813025640448/E6O6I011_400x400.png',},];

export default mentions;

cool, now import this file in your MyEditor Component.

import mentions from './mentions';

Now initialize this plugin

// mention plugin ( do it globally out of MyEditor class )const mentionPlugin = createMentionPlugin();const { MentionSuggestions } = mentionPlugin;

Now add this component as a sibling to MyEditor and wrap both these components into a div tag or react fragment.

<div><MyEditor ... /><MentionSuggestionsonSearchChange= { this.onSearchChange }suggestions= { this.state.suggestions }onAddMention= { this.onAddMention }/></div>

in constructor modify this line,

this.state = {editorState: EditorState.createEmpty(), suggestions: mentions};

implement these two methods

onSearchChange = ({ value }) => {this.setState({suggestions: defaultSuggestionsFilter(value, mentions),});};

onAddMention = () => { // add logic after mentioning someone here };

Cool, you are ready with @ mentions in your editor. Try typing @. This plugin required some extra work, other plugins available are very direct and easy to use. Simply import them, initialize them, and in the end, add as a sibling component to your MyEditor. That’s it, you are ready to use them. Check out the code in the example provided in my GitHub repo.

Now let’s see how I added Variable and BlockVariable Component which are atomic in nature i.e on backspace whole block gets deleted. So that the variable in the template didn’t get polluted. Check the Variable.js and BlockVariable.js files in the repo. The code is very simple if you go through it once, it will be clear.

The main concept used in them is

  1. Modifiers
  2. AtomicBlockUtils
  3. Entity

I hope this article will help you to get started with the draft-js. Draft-js has a lot of things, which you should try to find out. More you dive into it, more you can play with your editor. I will try to write a few more articles on draft-js advanced topics.

Some resources for getting started with it:

  1. Draft-js docs
  2. One of the good resource I found out.

Give a clap if you liked it and yes if you have any queries you can ask them down here I will try to help you out. Happy Coding 😃.


Published by HackerNoon on 2019/02/01