Building a Rich Text Editor with React and Draft.js, Part 2.3: ContentBlock Styling

Siobhan Mahoney
Jun 28, 2018 · 6 min read

This post is part of the second installment of a multi-part series about building a Rich Text Editor with React and Draft.js. For an introduction to the Draft.js framework with setup instructions, checkout Building a Rich Text Editor with Draft.js, Part 1: Basic Set Up. 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.

In this post, I will review how to add block-level styling options to your React text editor using tools built into the Draft.js API.

Draft.js can be installed with:

yarn add draft-js

For reference, I’ll be integrating these features into the React basic text editor I built for the demo discussed in the first post in this series, Building a Rich Text Editor with Draft.js, Part 1: Basic Set Up, the code for which is available on CodeSandbox.


Draft.js API Building Blocks

ContentBlock

As touched upon in my post covering how to build a key command and button for embedding links, a ContentBlock is a Javascript object that represents the full state of a single block of editor content, and together, are structured in an OrderedMap, which makes up the editor’sContentState.

The type property within a ContentBlock object indicates how the block should be rendered on screen. Draft.js provides several out-of-the-box block types associated with HTML elements, including:

  • header-one: <h1/>
  • header-two: <h2/>
  • header-three: <h3/>
  • header-four: <h4/>
  • header-five: <h5/>
  • header-six: <h6/>
  • blockquote: <blockquote/>
  • code-block: <pre/>
  • atomic: <figure/>
  • unordered-list-item and ordered-list-items: <li/>

In addition to these default options, Draft.js also allows for the creation of custom block components, which will be covered more in depth in a future post. If a block style is not specified, the content block type will default to unstyled.

To illustrate, checkout how the below text editor content is captured in the corresponding JSON objects:

Created using the JSON view tool on Draft-WYSIWYG

This tutorial will cover how to create easy access to these default block styles in our React editor’s interface.

Demo: Building UI Access to Block Styling Options

Setup

To keep our code organized and modular, let’s create separate components for the block style toolbar container and its different parts. Create a blockStyles directory within ./components and within it, create 3 files:

  1. BlockStyleToolbar.js: container component with logic block style definitions
  2. HeaderStyleDropdown: presentational component that will render a dropdown menu of different header style options
  3. BlockStyleButton:presentational component that will render a button for each of the non-header block style options

PageContainer Updates:

  • Import BlockStyleToolbar
import React from "react";
import { Editor, EditorState, RichUtils } from "draft-js";
import BlockStyleToolbar from "./blockStyles/BlockStyleToolbar";
  • Create the toggleBlockType function, which will get passed as a callback function when our block styles are selected:
toggleBlockType = (blockType) => {
this.onChange(RichUtils.toggleBlockType(this.state.editorState, blockType));
};
  • Update the render method to include BlockStyleToolbar and pass it EditorState and toggleBlockType as props
render() {
return (

{...}

<BlockStyleToolbar
editorState={this.state.editorState}
onToggle={this.toggleBlockType}
/>


{ ... }

<Editor
editorState={this.state.editorState}
handleKeyCommand={this.handleKeyCommand}
onChange={this.onChange}
/>
</div>
)
}

Next, let’s turn to our BlockStyleToolbar component and start it off by creating an array of styles. Each style will be represented by an object containing a label, which will be the text displayed in our UI and can be selected as you see fit, and style, which is a Draft.js key word that indicates how the content in your editor should be rendered.

As mentioned above, because I’d like my toolbar to organize theHeader levels separately from the other block style types, I’m going to create two separate objects, each of which will eventually get passed to and rendered in its own component:

export const BLOCK_TYPES = [
{ label: " “ ” ", style: "blockquote" },
{ label: "UL", style: "unordered-list-item" },
{ label: "OL", style: "ordered-list-item" },
{ label: "{ }", style: 'code-block' }
];
export const BLOCK_TYPE_HEADINGS = [
{ label: "H1", style: "header-one" },
{ label: "H2", style: "header-two" },
{ label: "H3", style: "header-three" },
{ label: "H4", style: "header-four" },
{ label: "H5", style: "header-five" },
{ label: "H6", style: "header-six" }
]

We’ll also create a getBlockStyle function, which we’ll import into PageContainer and pass to EditorState in order for the editor to detect and style as indicated:

  • getBlockStyle
export function getBlockStyle(block) {
switch (block.getType()) {
case "blockquote":
return "RichEditor-blockquote";
default:
return null;
}
}
  • PageContainer updates
import React from "react";
import { Editor, EditorState, RichUtils } from "draft-js";
import BlockStyleToolbar, { getBlockStyle } from "./blockStyles/BlockStyleToolbar";
class PageContainer extends React.Component {

{ ... }

toggleBlockType = blockType => {
this.onChange(RichUtils.toggleBlockType(this.state.editorState, blockType));
};

render() {
return (
<div className="editorContainer">

{...}

<BlockStyleToolbar
editorState={this.state.editorState}
onToggle={this.toggleBlockType}
/>

{...}

<Editor
blockStyleFn={getBlockStyle}
editorState={this.state.editorState}
handleKeyCommand={this.handleKeyCommand}
onChange={this.onChange}
/>

</div>
);
}
}
export default PageContainer;

Building the BlockStyleToolbar Component

We’ll then build out the BlockStyleToolbar, which will render its two children component components, passing to each the respective set of styles, as well as the EditorState, selectionState, and the selectionState’s current block type:

class BlockStyleToolbar extends React.Component {
render() {
const { editorState } = this.props;
const selection = editorState.getSelection();
const blockType = editorState
.getCurrentContent()
.getBlockForKey(selection.getStartKey())
.getType();

return (
<div>
<span className="RichEditor-controls">
<HeaderStyleDropdown
headerOptions={HEADER_TYPES}
active={blockType}
onToggle={this.props.onToggle}
/>

{BLOCK_TYPES.map(type => {
return (
<BlockStyleButton
active={type.style === blockType}
label={type.label}
onToggle={this.props.onToggle}
style={type.style}
key={type.label}
type={type}
/>
);
})}
</span>
</div>
);
}
}

Building Block Style Button Components

Next, we’ll create children components to render the header dropdown and other block style buttons, respectively.

HeaderStyleDropdown.js:

import React from "react";class HeaderStyleDropdown extends React.Component {

onToggle = event => {
let value = event.target.value
this.props.onToggle(value)
}

render() {
return (
<select value={this.props.active} onChange={this.onToggle}>
<option value="">Header Levels</option>
{this.props.headerOptions.map(heading => {
return (
<option value={heading.style}>
{heading.label}
</option>
)})}
</select>
)}
}
export default HeaderStyleDropdown

BlockStyleButton.js:

import React from "react"class BlockStyleButton extends React.Component {onToggle = (e) => {
e.preventDefault()
this.props.onToggle(this.props.style)
}
render() {
let className = "RichEditor-styleButton"
if (this.props.active) {
className += " RichEditor-activeButton"
}
return (
<span className={className} onClick={this.onToggle}>
{this.props.label}
</span>
);
}
}
export default BlockStyleButton

And there you go! Buttons and a dropdown select form for block styles:


Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade