Rich editor for your react typescript project using hooks

Gustav Coetzee
The Startup
Published in
5 min readFeb 17, 2020

Add titles, images and links to your draft.js editor

Photo by Tianyi Ma on Unsplash

TL;DR

So you need to give your users the ability to create articles on the fly? Your application is built in react typescript and you don’t use class based components. This tutorial will attempt at showing you exactly how that can be done. I will assume you already spun up a React app and you will be using material-ui for your dom framework/library.

Importing the right stuff.

Draft.js is a very powerful drafting library and with a lot of customisation options however it can be daunting task to try and understand all these functions and how they are used. The library has also changed a bit and v0.10.0 and up works differently from its predecessors. To make matters worse there are a lot of plugins available and getting them to work first time can pose its own set of challenges.

The focus here is to create a static toolbar for your editor that can be used to insert images, headings and links. You can do a lot more with draft.js and for that reason this guide categorises the functionality explained here into it own concerns that you can use however you see fit.

Getting started

The below extract is from the package.json file which will show you what dependencies you will need to install from npm. You can do so in your terminal from your project root using yarn.

default package installation versions as of time of writing 2020/02/16

Building the editor

Clone draft-js-editor if you would like to follow along from src. The first step is to create your editor element give it access to state as well as setting the focus.

But before we can do that we need to set a context for the editorState in order to have it accessible from outside the react component.

Import the EditorState type and initialise the editor state with empty then export EditorContext using the createContext hook

After we have created the context we can move to the editor component and import the types and methods we will need to build the editor.

List of imports used by the Article page
Create the editor component wrapped inside its context using the value from the state we will be setting on its properties all inside the element that sets focus to the editor. you can omit the blockRenderFn for now as it will be discussed later.

Our next task is to set the state of the editor and update that state when changes are incurred.

Use reference to editor component use callback to set the focus, create empty state and initialise with composite decorator (more on this later). Omit the composite decorator from the createEmpty because the strategy and component have not been built yet and will be covered later.

Up to this point we have finished setting up the boilerplate and you should be able to click below the placeholder and start typing.

Building a toolbar

Material ui labs has created a toggle button group component that we will use for our toolbar it is currently in alpha and that is why it needs to be imported from the lab.

in the example project we use the toggle button events and their values passed off to a switch statement that handles the state changes and dom interactions. Let’s do that now.

wrap icons inside the toggle button setting their values and wrapping inside the group with on change handler
handles the toggle buttons when clicked. Toggle buttons can take multiple values and the values are an array so for instance if you wanted to build bold, italic and underline into your toolbar it is certainly possible, the on and off toggle will stay selected until the toggle button group value is set to null. Which might help if you are planning on adding inline style to the toolbar.

with modifiers you are able to change content state but before we can do that let’s try and understand how draft.js works.

On a rudimental level editor state is responsible for managing the content state and the content blocks within the content state. When we update the editor state we do so by passing in a new content state which is made of various content blocks, these blocks have access to selection state and block scope amongst a whole host of useful methods. When you stringify a content state you would get a map back that has entities and blocks this map can be converted to raw and converted from raw to allow you to edit the article as well as displaying the article using html.

Modifiers

having a look at the handleToggleButtonGroup switch statement we pass “header-one” if the title button is clicked, then we modify the state and that returns a modified editor state which is bound to the editor state prop on the editor which in turn will make the currently selected block <h1></h1>. It is important to note what methods return; breaking the above example into pieces might help understand this.

const contentState = editorState.getCurrentContent;
const selectionState = editorState.getSelection;
const modifiedContentState = Modifier.setBlockType(contentState,selectionState,'header-one')
const newEditorState = EditorState.push(editorState,modifiedContentState,'change-block-type')setEditorState(newEditorState)

The setEditorState is responsible for re-rendering the react app and when updated the Editor component selection or cursor will now type in heading format.

AtomicBlocUtils

Moving to the “insert-image” case we can add the below code to our example which introduces us to atomic blocks and what they are used for.

The input on change event will take the first image file and update editor state with a new block called atomic block which can be customised to render images in the editor.

For the image to display on the editor we need to uncomment the

blockRendererFn={renderBlock}

on the Editor element add the renderBlock into the component.

we also need the media component that tells the editor how to handle the atomic block when encountered.

Whats happening here is when we have an image file we get the current content state from the editor state and insert an immutable/mutable entity. We further update the editorState with the entity using the utility function. When the re-render happens the block render function picks up the entity and displays the block as an image as was defined in the media component.

Draft utils plugins

To insert a link we will be making use of the draft-js-plugins-utils plugin, we will only be using this one plugin and you do need need to install any of the other plugins alongside however you will be required to create a type definition module for the plugin in question.

Create a textfield that will use the draft utils plugin and access the create link at selected method.

This is a good time to uncomment the

new CompositeDecorator([   {    strategy: linkStrategy,    component: DecoratedLink   } ]))

that was initialised on the editor state as we will create the needed files for it now.

Create the DecoratedLink needed to create a link that is able to take a display value and a url
Create the link strategy for the composite decorator.
Initialise a selected rectangle object on the component that is able to track where the link will be inserted
Track the state of the cursor with a use effect, that will only update if the current selection is not null, thus when you select text and click the insert link button the last tracked state will point to the selected range inside the editor
Create the type definitions for the plugin in your root directory in order for us to use the plugins util and create link method, this is a .d.ts file which will be used as a type definition for the draft utils plugin with your typescript project.

What we are doing here is tracking the current position of the cursor and when the cursor has a selected range by checking

disabled={editorState.getSelection().isCollapsed()}

you are able to click on the insert link button which will set the toggle to insert a link a the last recorded position from getVisibleSelectionRect that was inside the editor, then using a simple

display: toggleButtonGroupValue === "insert-link" ? "block" : "none"

the text field will display and you can enter the url for your desired link. When clicking on the check icon the program will use the

draftUtils.createLinkAtSelection(editorState, anchorElUrl)

method that has been imported to create the needed content state and modifications and update the editor state, in turn the composite decorator will scan the content of the editor and where it finds the link strategy it will insert your link into that block.

--

--