Best practices when working with draft js

Sun, Jun 25, 2017 - 5 minute read

This is a collection of tips and opinions on using draft-js. Given the framework's youth and lack of guidance on the internet, I feel like sharing some of my experience in the hope it'll be useful to others starting out.

This post is part of a series:
  1. Getting started with draft.js
  2. Getting started with draft.js plugins
  3. Draft-js - Saving data to the server
  4. Draft-js and Redux - the perfect couple
  5. Draft-js building search and replace functionality
  6. Best practices when working with draft js (current)
  7. Drawing on canvas inside a Draft js document

Disclaimer: It’s hard to exclude my personal opinion from this blog post, but I’ll try as best I can. Please give me feedback, whether you think I’m wrong or you agree with me, or if you want to help submitting a pull request to the draft.js docs containing some known best practices.

Doing work for clients on draft.js apps in production and also talking to people on community chat I’ve seen myself and other people make the same mistakes. So I figured it’s time to start writing down what I have learnt so far, with the hope of helping others.

1. React and redux are as slow as their competing frameworks, unless you use it right.

In conversations with community members I’ve heard reports that redux slows their app down when used in combination with draft.js. This really surprised me because it hadn’t happened to me so I dug in. Here’s a common scenario:

  • There’s a react component containing the draft.js Editor, this component is connected with the redux store, which stores the editor’s state.
  • The state gets updated every time the editor fires an onChange event.
  • The editor state makes it’s round trip, but there appears to be a performance penalty when doing so, there’s a considerable delay for every key press.

In every case the problem wasn’t related to using draft.js or redux - The most common performance problem that react apps suffer is this: Rendering too much at the same time.

When using react with redux, the connect method implements shouldComponentUpdate for you, so generally you don’t have to worry about optimizing a components state update, that is as long as you only give it the state it needs. The connect higher order component will compare the props object it’s given with strict equality. So if only one of the props has changed, the component and all it’s children will re-render. If you’re subscribing your component to data it doesn’t need, it’s highly likely that unintentional renders will occur and that’s what will affect your apps performance.

Check out this excellent article on building performant react apps by Fran├žois Zaninotto to learn more about optimizing your app, it’s a must read for every react developer!

2. Only use convertToRaw and convertFromRaw when absolutely necessary

convertToRaw and convertFromRaw are used for converting draft.js state into serializable objects and vice versa, making your contentState persistable. These are expensive operations and should only be used when absolutely necessary. It’s absolutely fine to store your editorState in your redux store, you don’t have to serialize it (I’m saying this because I’ve seen people implement it this way).

Typically, you’d use convertToRaw to dispatch your state to the server, and convertToRaw once you fetched it.

3. Always use the children prop to render editable text

For custom components and decorator components, you should always use the children prop to render editable text. The children prop contains not only said text, the text is wrapped in components like EditorBlock and EditorLeaf. This is needed to maintain editing functionality inside custom components.

4. Just like any react component, you can optimize your decorators and custom block components with shouldComponentUpdate.

A feature-rich draft.js editor with lots of decorators and custom block components can easily cause performance issues, especially for large documents. Draft.js doesn’t do anything special to prevent unnecessary rendering of these components, you can however optimize by using the shouldComponentUpdate method, which puts you in control of when your components re-render.

Here’s an example of a shouldComponentUpdate implemented for a custom block which is expensive to render

shouldComponentUpdate(nextProps) {
  return this.props.children.props.block.getText() !== nextProps.children.props.block.getText()

This simply compares the current text content with the last text content. Early optimization however can be a timesink. If your app doesn’t have performance problems, it’s probably too early to look into optimization!

5. There’s no good way to implement tables right now.

Draft.js’s content model is flat. That’s a restriction that comes with it’s pros and cons. Nested structures are harder to optimize and reason about, so this makes maintaining draft.js easier. However, this restriction means that implementing a table layout is hard to do without nesting editors. Nested Editors, although doable and probably the best wa to implement table functionality in draft.js roight now, comes with a bunch of extra complexity, try to stay away from this if you can.

There is an ongoing effort to implement a tree structure in draft-js but we have no idea as to when this feature will actually land.

6. Be careful with your architecture

Developing an architecture for a draft.js application is hard. There’s not much advice out there and the draft.js website unfortunately lacks documentation about how to best structure your application. With draft.js it’s easy to get into a vicious cycle of building complex component state and highly coupled functionality.

This is where draft-js-plugins comes to the rescue indeed - because you not only get lots functionality out of the box. You also get a get a simple architecture with it and that value is not to be underestimated. The cognitive burden of designing a system that encourages modularity with draft.js is more or less solved with this framework. It’s fairly straight forward to write your own plugin with draft-js-plugins. Whenever I get serious about writing a complex draft.js based app, draft-js-plugins is a must have.

7. Consider getting involved

Consider joining the draft.js slack or the reactiflux draft-js channel, there’s always some great, friendly people just like you, who want to figure stuff out and improve things!

Right, that’s it. Again, if you have any questions, suggestions, or just want a bit of friendly banter, get involved in the draft js slack or in the draft-js channel on reactiflux or chat to me directly.

Wow you reached the end of the page! Could that mean that you're interested in working together? Please get in touch early to avoid disappointment! My availabilty tends to fill up quickly.

Chat with me here or hit me up on Twitter