loading...
Cover image for This Blog Post Was Written Using Draft.js

This Blog Post Was Written Using Draft.js

ben profile image Ben Halpern ・5 min read

Procrastination can be a wonderful thing in programming: Wait around long enough and the open-source universe will present itself to you. For quite some time I had been meaning to recreate the in-browser document editor on the backend of this website from TinyMCE to something that was less of an opinionated "product" and more extensible so I could add syntax highlighting, toggling between wysiwyg, markup and markdown, and whatever interesting features came to mind in the future, and in a sane manner. Ideally it would play well with ReactJS, since that is what I wanted to use for the dynamic parts of this website and is absolutely my go-to environment for implementing complex UI.

And then Draft.js, a React framework for building text editors, was announced. It does not just provide everything I need out of the box, so it is going to be some hard work to implement the features I want, but it is the right choice. Since the project already has almost 4,000 stars on GitHub, and is used in production by Facebook on major projects like Notes and Messenger, I can expect that this is a Framework that will evolve for the better and remain stable. The React ecosystem is a really great one to be a part of and I am happy to be using something that not only plays well, is built to make perfect use of the application lifecycle.

React is the right choice for rich text editing

Text editing is a fundamental state synchronization issue. As I make modifications to a portion of the document, I want those changes to be reflected in the transpiled markup without any loss of state or potential for asynchronous outcomes and it needs to happen efficiently. Declaratively rendering the entire document using intelligent diffing based on one central state is the perfect pattern for this. The longer a user stays in an application and the more interactions they have with that application, the bigger the risk of state asynchronization becomes. React is explicitly built to make this less of an issue and is ideologically aligned with the end goal of UI stability.

The React community gives me confidence

The number of people ready to jump in on any big project the React team announces makes working within the environment a pleasure. By the time I got wind of Draft.js, the same day it was announced, I already saw Github issues relating to questions I had about the framework. I quickly found a project making ground on transpilation from Draft.js blocks to markup, which inspired my solution. Even though Draft.js is new, the combination of community support and the backing of a 300 billion dollar company made me feel very comfortable spiking on it immediately.

My Basic Implementation

I rushed to get this into my code because the timing was perfect and I was excited to use it, but right now it is fairly basic and missing a lot of the features I will be implementing over the next few weeks or so. But I am confident I will be able to gradually add the features I need while gaining a better understanding of this flexible API. I made immediate use of the convertFromRaw and convertToRaw methods for transpilation to and from HTML, though it is a work in progress. I used of the RichUtils in order to help manage the complex rich text styling. I have not yet examined the depth of a lot of this functionality but plopping it in was easy enough. Although some of the concepts of Draft.js are complicated, getting started by looking at the examples was quite straightforward.

Here is my render() method, with the code mostly lifted straight from the Draft.js rich editor example code.

render() {
  const {editorState} = this.state;
  let className = 'RichEditor-editor';
  var contentState = editorState.getCurrentContent();
  let markedUpBlocks = backdraft(convertToRaw(contentState), MARKUP);
  if (!contentState.hasText()) {
    if (contentState.getBlockMap().first().getType() !== 'unstyled') {
    className += ' RichEditor-hidePlaceholder';
    }
  }

  return (
    <div className="RichEditor-root">
      <BlockStyleControls
        editorState={editorState}
        onToggle={this.toggleBlockType}
      />
      <InlineStyleControls
        editorState={editorState}
        onToggle={this.toggleInlineStyle}
       />
      <div className={className} onClick={this.focus}>
        <Editor
          blockStyleFn={getBlockStyle}
          customStyleMap={styleMap}
          editorState={editorState}
          handleKeyCommand={this.handleKeyCommand}
          onChange={this.onChange}
          ref="editor"
          spellCheck={true}
         />
        </div>
      <div>
        <input type="hidden" name="article[body_html]" id="article_body_html" value={markedUpBlocks.join("")} />
      </div>
    </div>
  );
}

Why do I need such a complex editor?

This is very much a project of interest for me and I have run into numerous use cases that correlate with the problems that Draft.js tries to solve. Those problems include things like dynamically generating content such as highlighted tags as a user types, organizing rich data alongside text in an intuitive manner and other similar situations. Dynamic rich text editing is useful more often than we realize and I think developers settle for flimsy implementations a lot of the time. This project involves full-on rich classical document text-editing, which in my opinion is “base case” and a great place to start given the interface I had in mind, but the areas in which this sort of interactivity can play a big role are many. This API is going to be an important one for web development and I am excited to learn it.

In conclusion

Getting this implemented was straightforward, with a gentle and progressive learning curve. It was very nice to have something useful after to work with without even going past the tip of the iceberg in learning this framework. It seemed silly at first that Facebook has not provided some basic markup and markdown transpilation but the reasoning is that the different requirements for each use case make it unrealistic to offer a one-size-fits-all solution. This makes sense for me and I believe the basic API for which to build on for these functionalities works wonderfully. I will open-source anything good that comes from my efforts and I encourage others to do the same. I expect in not too long there will be many plug and play options for all the different data conversion needs. Leaving this to the community of excited React developers has lots of potential.

Discussion

pic
Editor guide