Rich text editing on the web: Getting started with Draft.js
Tags: react, javascript, draftjs, tutorial
Published: 2019-10-23 01:47:05

Once upon a time, not too long ago, rich text editors on the web were a huge challenge to implement. There were options, the most popular being [TinyMCE](https://www.tiny.cloud), which came out in 2004, but it was an area that had a lot of room to be improved.

Fast forward to 2016: Facebook drops a new open source rich text editor as a companion to their wildly popular React library called [Draft.js](https://draftjs.org)

Fast forward to today: There are many great rich text editor options ([Slate](https://github.com/ianstormtaylor/slate), [Quill](https://quilljs.com), [tiptap](https://tiptap.scrumpy.io), and [ProseMirror](http://prosemirror.net) to name a few) out there. You can pick your favourite! 

As you can tell from the title, I’m going to be writing about how to use Draft.js. So if your favourite is one of the other options I linked, you can just stop reading now 😉 😛 

- - - -

## Purpose of this post/series of posts
I am hoping that this is the first of a series of blog posts on working with Draft.js. It’ll start off with the basics, and as I continue, the posts will become a bit more advanced.

👩‍💻 I am not the #1 expert on Draft.js, but I do work with it pretty regularly and have come up against a variety of weird situations in which I need to write some custom code to handle various specific requirements, as well as search out 3rd party libraries to extend Draft.js’s base functionality.

I am hoping to share some of what I have learned in a way that someone googling for a specific answer can find what they are looking for, but also if you are just a developer starting out and want to play around with Draft.js, you can follow along with these posts and do so.

- - - -

## What will I be building today in this article?
[This very simple Draft.js demo is what we will be building](https://www.rosey.dev/demos/draft-article-1/) 

- - - -

## Enough with the introduction text, let’s get started!
⚠️ Warning: Draft.js is a framework meant to be used with [React](http://reactjs.org), so if you already have a project you want to add a rich text editor to, but it’s written using a different library, like Vue, you may want to look at more suitable editor options. [JefMari/awesome-wysiwyg](https://github.com/JefMari/awesome-wysiwyg) is a great resource for looking at all your options.

💁‍♂️ If you don’t already have a project that you want to add Draft.js to, you’ll need to get one started.  I don’t want to waste your time having to get something all set up, so if you need a quick-start for working with Draft and don’t already have your own workflow in place for quickly starting a new project, I suggest you go to [facebook/create-react-app](https://github.com/facebook/create-react-app) and follow the instructions there.  Set up should be very speedy! 💨

😅 OK so much set up and intro text, I am sorry 🙂 Just want to make sure this is as accessible as possible for all levels of developers!

### Installing the draft-js dependency

Before you can start using Draft.js you need to install it using your package manager. Here is how that looks with `npm` in the terminal -

```bash
npm install --save draft-js
```

### Rendering the editor in your app
From there, in the component where you want to render the draft editor, you’ll need to import two modules: The editor component itself, and `EditorState`, which is what we’ll use to build out and modify the editor’s content. (⚡️ Hint: If you feel like this is not enough information for you, you can always check out the [official documentation on EditorState](https://draftjs.org/docs/api-reference-editor-state))

```javascript
import { Editor, EditorState } from 'draft-js';
```

_What exactly is EditorState?_
The Draft editor uses EditorState to know exactly what needs to be rendered. It contains all information on what text is written, how it is formatted, and whether any text is currently selected.

It is immutable! [Immutable](https://en.wikipedia.org/wiki/Immutable_object) means that you cannot make changes to it. In other words, **every time you make a change to the content in the editor, a new instance of EditorState is created.** This is important to know and I think an easy place to get tripped up and confused if you are just starting out.

EditorState has a number of static methods defined that you can use to create the initial EditorState for your editor, as well as modify content later on.

The general pattern with EditorState is this:

1. You have a wrapping component. On its state object, it has an `EditorState` instance.
2. The wrapping component renders an Editor component and passes in `editorState` as a prop.
3. The wrapping component also defines an `onChange` method that accepts an instance of `editorState` as an argument. Whenever this method gets called, it updates its own state to be the new instance of `EditorState`
4. The wrapping component passes `onChange` to the Editor as a prop. The Editor will call it whenever the editorState needs to be updated.

If you are using `create-react-app` you can open `src/App.js` and replace its contents with this:

```javascript
import React from 'react';
import './App.css';
import { Editor, EditorState } from 'draft-js';
class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      editorState: EditorState.createEmpty()
    };

    this.onChange = this.onChange.bind(this);
  }

  onChange (editorState) {
    this.setState({editorState});
  }

  render() {
    return (
      <div className="my-little-app">
        <h1>Playing with Draft.js!</h1>
        <div className="draft-editor-wrapper">
          <Editor
            editorState={this.state.editorState}
            onChange={this.onChange} />
        </div>
      </div>
    );
  }
}

export default App;
```

This code is basically an exact code representation of the 4 bullet points I outlined above 🙂 I added a few wrapping divs and headers just ‘cause I think they may be useful later.

The one extra thing you may see is that in the constructor we set up an initial instance of `editorState` using `EditorState.createEmpty()`. This is one of those static methods I mentioned. As you can probably tell from the naming, it creates an empty instance of EditorState for us to start with, meaning no text, no formatting, no nothing 🙂 a blank slate!

If you’re using `create-react-app` and load up the app at this point, the editor is there, but it’s hard to tell because the page is completely unstyled and the editor is empty. I updated `App.css` to look like this just so that it’s a bit easier to see what’s going on:

```css
.my-little-app {
  width: 500px;
  margin: 50px auto;
}

.draft-editor-wrapper {
  border: 1px solid #ccc;
}
```


### Ok, the editor is rendered! But it’s boring!

Try typing something and it works! You are done! Sort of. 😬

You may notice that although you are now technically using a rich text editor, it… isn’t very rich. 🎨

Draft.js has the power to make text bold, italic, etc, but by default it won’t do any of that unless you tell it to.

There are a few ways to go about adding this functionality. We could add some buttons that you click to insert formatting! Or we could add some keyboard shortcuts, like `cmd-b` (`ctrl-b` for you windows and linux users) to make text bold. Draft.js offers a module called `RichUtils` that can handle a lot of this for you. (⚡️ Reminder: If you feel like this is not enough information for you, you can always check out the [official documentation on RichUtils](https://draftjs.org/docs/api-reference-rich-utils))

Since this is just a post about getting started with the basics, let’s start off simple and make a couple of buttons for some basic styles: Bold and Italic.

We need to update our `import` to also include the `RichUtils` module -
```javascript
import { Editor, EditorState, RichUtils } from 'draft-js';
```

Then I’m going to add a couple of buttons above my draft editor, and add `onMouseDown` listeners to them.

My `App.js` now looks like this:

```javascript
import React from 'react';
import './App.css';
import { Editor, EditorState, RichUtils } from 'draft-js';
class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      editorState: EditorState.createEmpty()
    };

    this.onChange = this.onChange.bind(this);
    this.toggleInlineStyle = this.toggleInlineStyle.bind(this);
  }

  onChange (editorState) {
    this.setState({editorState});
  }

  toggleInlineStyle (event) {
    event.preventDefault();
    let style = event.currentTarget.getAttribute('data-style');
    this.setState({
      editorState: RichUtils.toggleInlineStyle(this.state.editorState, style)
    });
  }

  render() {
    return (
      <div className="my-little-app">
        <h1>Playing with Draft!</h1>
        <input
          type="button"
          value="Bold"
          data-style="BOLD"
          onMouseDown={this.toggleInlineStyle}
        />

        <input
          type="button"
          value="Italic"
          data-style="ITALIC"
          onMouseDown={this.toggleInlineStyle}
        />
        
	<div className="draft-editor-wrapper">
          <Editor
            editorState={this.state.editorState}
            onChange={this.onChange} />
        </div>
      </div>
    );
  }
}

export default App;
```

#### Some quick questions and answers here:

_Why onMouseDown and not onClick?_
Because we don’t want the Draft editor to lose its focus, so by listening onMouseDown and calling `event.preventDefault()` we stop that event from bubbling up and causing the editor to lose focus. So we can toggle styles and continue typing without having to click back into the editor first.

_Why set a data-style on the element instead of making separate methods for each style?_
Personal preference 🙂 I just thought it was nicer to have a single `toggleInlineStyle` method and infer which style based on the attribute on the element clicked.

_Tell me what is happening in the `toggleInlineStyle` method!_

First, we call `preventDefault` on the event so that we don’t blur the editor, as I previously mentioned.

Then we grab which style is being toggled from the element clicked.

Then we use the magic `RichUtils` ’s `toggleInlineStyle` method.

It takes the first argument as `editorState` and the second argument as a string that tells it which style to apply.  It then returns a brand new `editorState` .

We then need to use this **new** `editorState` and call `setState` to update our _existing_ `editorState`.

Calling `setState`, as you may know if you are familiar with React, will trigger a re-render which will then cause that new `editorState` to be passed to the Editor component as a prop and voila! The style has been toggled.

I read this sentence several times and worried it was too wordy and confusing. I tried to tweak it a bit. Hopefully it made sense to you 🙃

_What inline styles does RichUtils support?_

Out-of-the-box [the following styles](https://github.com/facebook/draft-js/blob/master/src/model/immutable/DefaultDraftInlineStyle.js) are supported:

* `BOLD` 
* `CODE`
* `ITALIC`
* `STRIKETHROUGH`
* `UNDERLINE`

You can also define your own inline styles, but that’s a more advanced topic 🙂 

### I think that’s enough for one blog post

You are probably bored and/or tired by now and so am I 😀

If you’ve been following along using `create-react-app` boilerplate you should have something like this by now:

[Simple Draft.js Demo](https://www.rosey.dev/demos/draft-article-1/) 

I know this was a pretty basic start and I promise I will get into more interesting things as I write more of these posts. Here’s some topics I’d like to cover, and I’m sure there will be more 🙂 

* Placeholder value when the editor is empty
* Pre-populating an editor with something other than an empty value
* Keyboard shortcuts
* Block formatting (blockquote, unordered list, etc)
* Custom styles and custom entities
* Soft newlines vs hard newlines
* Saving to the back end
* Rendering saved content outside of the editor itself
* Adding emoji autocomplete and other fun extras
* Manipulating content in more weird and wonderful ways (for example, maybe you want cmd-b to wrap text in the markdown `**text**` instead of applying an actual bold style. This is a thing that can be done, if you so desire.)
* Have ideas for stuff you'd like to see? Let me know in the comments!

Thanks very much, and I hope that this has been useful for some of you! 👋