Integrating Summernote with React

Integrating Summernote with React

This is another of those posts that is reference material for myself whilst also potentially being helpful for others.

Summernote is a great WYSIWYG editor based on bootstrap.

I was looking for an editor that I could integrate into a product to make it easier for non tech people to write content. I was reinventing the wheel and building a CMS explicitly with the intention of making things super simple for content writers.

I stumbled upon Quill and StackEdit but they seemed overly complex for my needs and I encountered some issues within moments of playing around with them. Summernote was super simple, looked good, was easy to use and 'just worked'.

When I saw that react-summernote existed I was sold.

Not so fast

Unfortunately nothing is ever that easy and there is definitely a certain irony to the process of making something simple being super complex.

Whilst react-summernote did the job in terms of containing summernote within a react component, I encountered two issues.

  • CodeMirror and the codeview aspect of summernote did not work. Clicking the toolbar button simply displayed a blank screen.
  • It was not going to be easy to install 3rd part summernote plugins that were not React-ified.

I battled away and eventually resolved both of these issues. Here is how.

Webpack

Part of the issue (for me) was that react-summernote is built with webpack. Whilst I know webpack is great I have never used it myself as I have too much legacy stuff based around gulp and browserify. This seemed like a good opportunity to investigate webpack.

The webpack homepage

I forked react-summernote and had a play. When I initially built it I got various console errors telling me that CodeMirror was not defined.

I stumbled upon this issue in the summernote repository pertaining to webpack and CodeMirror not playing nicely together.

I did not deep dive into the 'whys' but decided that I would not include CodeMirror in the bundle, would include it on my page separately (served from a CDN) and would debug from there.

I achieved this with the following code in my webpack config (as described in the above issue):

const config = {
  // ... rest of webpack config
  plugins: [
    new webpack.IgnorePlugin(/^codemirror$/),
  ],
}

After having removed CodeMirror from my built Javascript I was still encountering issues with CodeMirror being undefined even though I was explicitly including the CodeMirror javascript in my page. I knew it was defined globally and could see that it was in the developer console.

Upon investigating, I found the (below) code snippet in the summernote.js npm module file.

var CodeMirror;
  if (agent.hasCodeMirror) {
    if (agent.isSupportAmd) {
      require(['codemirror'], function (cm) {
        CodeMirror = cm;
      });
    } else {
      CodeMirror = window.CodeMirror;
    }
  }

Independent of the 'whys' (again) some console.log calls allowed me to discern that my globally defined CodeMirror was being set to null within the react-summernote scope and that agent.hasCodeMirror was not set.

Commenting out the above code and rebuilding react-summernote gave me a javascript file that did not contain the CodeMirror source code and that worked.

Obviously it is sub optimal not understanding exactly why things were broken but at the end of the day often times, being honest, the reason I use libraries is because I simply want to avoid complexity and avoiding having to understand. Building a WYSIWYG editor is incredibly complex - there is a reason that I do not want to do it myself. When I have more time I might investigate this in more depth.

Plugins

Next I wanted to implement a custom plugin. I stumbled upon summernote-addclass.

The README explains how to set it up with standalone summernote but I wanted to use it with react-summernote.

Simply including the source file led to console errors because it was not being loaded within the correct scope to give it access to the things it needed to 'bind' it to summernote (which was built into the compiled Javascript from react-summernote).

The resolution was simply to import the plugin Javascript right after summernote in the react-summernote code (rather than including standalone js source code) and then rebuild. Easy. In fact, easier than I had expected

import 'summernote/dist/summernote';
import '../plugins/summernote-ext-addclass';

Bonus

Whilst the plugin worked as written. It did not work well in that I quickly discovered that when a class was being added/removed the change was not being passed to the callbacks defined in react-summernote and passed to the component.

I had to manually edit the plugin to call the onChange callback of the react-summernote component such that the HTML content state property (which I would then pass to the server) would be updated.

This was as simple as calling the following code:

context.options.callbacks.onChange(context.code());

Again, I worked this out with a very primitive understanding of summernote plugins. By the looks of things they have created a very intuitive/simple API for creating custom plugins - everything you need is providing within that context object.

I hope that this helps point you in the right direction if you are struggling with any similar issues.


Thomas Clowes

Thomas Clowes

I am a 28 year old software engineer from the United Kingdom. During the day I build multi platform applications. In my spare time I eat food and run marathons. Sometimes I write angry tweets.