Building a Rich Text Editor with React and Draft.js, Pt. 1: Basic Set Up
This is the first installment of a multi-part series about building a Rich Text Editor with React and Draft.js. Links to additional posts in this series can be found below. The full codebase for my ongoing Draft.js project is available on GitHub. Feel free to checkout the deployed demo here.
Draft.js is a Javascript rich text editor framework designed for React. In this post, I will explain why Draft.js is such a powerful tool and provide a high-level overview of the core features of its API. I will also go over the steps for setting up Draft.js in your React App and provide instructions for adding basic rich text functionality.
Table of Contents:
I. Background
III. Basic Set Up
IV. Adding Basic Rich Text Functionality
I. Background
Introduced in 2016, Draft.js was built by Facebook and served to tackle a number of issues that accompanied to capture a user’s posts and comments, namely with the use of <textarea>
and <div>
tags. As noted in a post published on Facebook’s engineering blog, this method required a number of work-arounds to manually track highlighter and cursor positions — a total nightmare from a developer’s perspective.
In addition to the strain on engineering time and energy, the lack of metadata captured in plain text led to issues with state and DOM synchronization. The constraints of plain text could also be felt by the enduser, who could not enjoy rich styles nor embed content in their posts and comments.
As a result, the foundation of Draft.js is the immutable data structures that represent its editor state and a handler to receive state updates from within the editor component, mirroring the controlled DOM input API. This single top-level immutable object serves as a snapshot of the full state entailing the contents, cursor, and undo/redo stacks among other values needed to represent the editor.
II. API Core Features
The cornerstone of Draft.js is its Editor
React component and its 2 default props, EditorState
and onChange
.
EditorState
is a single immutable object that represents a complete snapshot of the state of the editor, including contents, cursor, and undo/redo history. All changes to content and selection within the editor will create newEditorState
objects.onChange()
is a callback function that implements any changes that occur in the editor DOM with the currentEditorState
object and provides this updated state value to the top level of theeditor
core.
III. Basic Set Up
Let’s begin by creating a new React app:
npm install -g create-react-app
create-react-app draftjs-demo
cd draftjs-demo
Now that we’ve initialized our app, let’s install Draft.js:
yarn add draft-js
Now that Draft.js has been added as a dependency, let’s get started setting up our app.
To keep things organized, I’m going create a PageContainer
component that I will work from.
To begin, let’s import Draft.js and add the core Editor
component, EditorState
, and onChange
:
- Import the
Editor
andEditorState
class from draft-js
2. Render theEditor
Component within PageContainer
:
3. Setting up EditorState
: Create a constructor()
that initiates EditorState
within the component’s state
, sets its content to empty, and pass it to the Editor
component as a prop:
4. Define onChange
function within the container component and pass to Editor
component as prop:
You should now have a functioning text editor rendering in your browser:
IV. Adding Basic Rich Text Functionality
Now that our basic Editor component is up and running, lets start exploring some of the fun rich text styling and decorators Draft.js has to offer!
RichUtils and Key Commands
The RichUtils
module offers a number of useful functions for inline and block style options.
SinceRichUtils
has information about the core key commands (e.g., Cmd+B
(bold), Cmd+I
(italic), and Cmd+U
(underline)), we can add functionality to handle these commands using thehandleKeyCommand
prop, and hook these into RichUtils
to apply or remove the desired style.
Here’s how to add it in your app:
- Update Draft.js imports to include
RichUtils
:
import { Editor, EditorState, RichUtils} from 'draft-js';
2. Define handleKeyCommand
method:
handleKeyCommand = (command) => {
const newState = RichUtils.handleKeyCommand(this.state.editorState, command) if (newState) {
this.onChange(newState);
return 'handled';
} return 'not-handled';
}
Here, we are passing a command (like bold
or underline
) as an argument, which will get passed to the RichUtils.handleKeyCommand
, which handles key commands out of the box, along with the current EditorState
object. If an updated EditorState
is returned as a result, we will then pass the updated new EditorState
object to the onChange
method, which in turn will update the state.
3. Finally, we pass the handleKeyCommand
to the editor
component as a prop in order for it to function within our text editor.
<Editor
editorState={this.state.editorState}
handleKeyCommand={this.handleKeyCommand}
onChange={this.onChange}
/>
For reference, here is how your container component should look after following these steps:
RichUtils Buttons
So we can toggle text style via key commands. Awesome. But wouldn’t a button for these commands make for a richer user experience?
Answer: Of course it would, which is why the RichUtils
module includes the static method toggleInlineStyle
that will take care of it.
To create inline style buttons, create a function for each style button. Each function should pass the editorState
object and the type of style command to RichUtils.toggleInlineStyle
, which in turn will get passed to onChange
. Here’s an example that will italicize the selected text:
onItalicClick = () => {
this.onChange(RichUtils.toggleInlineStyle(this.state.editorState, 'ITALIC'))
}
Then, create a button with an event handler that takes, e.g., onItalicClick
as a callback function.
<button onClick={this.onItalicClick}>
<em>I</em>
</button>
Here is how the container element will appear after adding buttons for underlining, italicizing, and bolding fonts: