DEV Community

Cover image for Implement ToastUI Editor with Next.JS (w/ TypeScript)
Brandon Wie
Brandon Wie

Posted on

Implement ToastUI Editor with Next.JS (w/ TypeScript)

To make it as brief as possible, this post will only deal with some of the issues that you might encounter while implementing ToastUI Editor inside Next.JS projects.

#1.ReferenceError: self is undefined

self is undefined error
This is not only a matter of ToastUI Editor, but also with libraries that need the window object on runtime. It is because Next.js will render it on the server-side, but there is no window object exists.

// toast-editor.js
(function webpackUniversalModuleDefinition(root, factory) {
    if(typeof exports === 'object' && typeof module === 'object')
        module.exports = factory(require("prosemirror-commands"), require("prosemirror-history"), require("prosemirror-inputrules"), require("prosemirror-keymap"), require("prosemirror-model"), require("prosemirror-state"), require("prosemirror-transform"), require("prosemirror-view"));
    else if(typeof define === 'function' && define.amd)
        define(["prosemirror-commands", "prosemirror-history", "prosemirror-inputrules", "prosemirror-keymap", "prosemirror-model", "prosemirror-state", "prosemirror-transform", "prosemirror-view"], factory);
    else if(typeof exports === 'object')
        exports["toastui"] = factory(require("prosemirror-commands"), require("prosemirror-history"), require("prosemirror-inputrules"), require("prosemirror-keymap"), require("prosemirror-model"), require("prosemirror-state"), require("prosemirror-transform"), require("prosemirror-view"));
    else
        root["toastui"] = root["toastui"] || {}, root["toastui"]["Editor"] = factory(root[undefined], root[undefined], root[undefined], root[undefined], root[undefined], root[undefined], root[undefined], root[undefined]);
})(self, function(__WEBPACK_EXTERNAL_MODULE__695__, __WEBPACK_EXTERNAL_MODULE__412__, __WEBPACK_EXTERNAL_MODULE__479__, __WEBPACK_EXTERNAL_MODULE__481__, __WEBPACK_EXTERNAL_MODULE__43__, __WEBPACK_EXTERNAL_MODULE__814__, __WEBPACK_EXTERNAL_MODULE__785__, __WEBPACK_EXTERNAL_MODULE__311__)
Enter fullscreen mode Exit fullscreen mode

In short, the self at the bottom left of the code above causes the error. ToastUI needs window object to initiate

// line 17364 on lib.dom.ts (a part of ToastUI Editor)
declare var self: Window & typeof globalThis;
Enter fullscreen mode Exit fullscreen mode

So how to fix it?

Next.js supports ES2020 dynamic import() for JavaScript, which allows you to disable SSR.

Your code should look something like this:

import React, { MutableRefObject } from 'react';
import dynamic from 'next/dynamic';
import { EditorProps, Editor as EditorType } from '@toast-ui/react-editor';
import { TuiWithForwardedRefProps } from './EditorWithForwardedRef';

const Editor = dynamic<TuiWithForwardedProps>(
  () => import('@components/ToastEditor/EditorWithForwardedRef'),
  {
    ssr: false,
  }
);

const EditorWithForwardRef = React.forwardRef<
  EditorType | undefined, // object type
  EditorProps // prop type
>((props, ref) => (
  <Editor {...props} forwardedRef={ref as MutableRefObject<EditorType>} />
));
EditorWithForwardRef.displayName = 'EditorWithForwardRef'; // throws error if not set

interface ToastUiEditorProps extends EditorProps {
  forwardedRef: MutableRefObject<EditorType | undefined>;
}
const ToastEditor: React.FC<ToastUiEditorProps> = (props) => {
  return (
    <EditorWithForwardRef
      {...props}
      ref={props.forwardedRef}
      initialEditType={props.initialEditType || 'wysiwyg'}
      height={props.height || '300px'}
      previewStyle={props.previewStyle || 'vertical'}
    />
  );
};

export default ToastEditor;
Enter fullscreen mode Exit fullscreen mode

The code below is what is imported right above the inside Editor variable. You may find further hints etc in the link at the bottom.

import React, { MutableRefObject } from 'react';
// editor
import { Editor, EditorProps } from '@toast-ui/react-editor';
import '@toast-ui/editor/dist/toastui-editor.css'; // Editor's Style
// table
import tableMergedCell from '@toast-ui/editor-plugin-table-merged-cell';
// code syntax highlight
import Prism from 'prismjs'; // main library for coloring
import 'prismjs/themes/prism.css';
import 'prismjs/components/prism-javascript';
import 'prismjs/components/prism-python';
import '@toast-ui/editor-plugin-code-syntax-highlight/dist/toastui-editor-plugin-code-syntax-highlight.css';
import codeSyntaxHighlight from '@toast-ui/editor-plugin-code-syntax-highlight';
// 3 below for editor-plugin-color-syntax
import 'tui-color-picker/dist/tui-color-picker.css';
import '@toast-ui/editor-plugin-color-syntax/dist/toastui-editor-plugin-color-syntax.css';
import colorSyntax, {
  PluginOptions,
} from '@toast-ui/editor-plugin-color-syntax';
// Korean lang
import '@toast-ui/editor/dist/i18n/ko-kr';

export interface TuiWithForwardedRefProps extends EditorProps {
  // using type ForwardedRef instead of MutableRefObject causes error when using useRef();
  forwardedRef?: MutableRefObject<Editor>;
  // type for color syntax - array of color strings
  colorSyntaxOptions?: PluginOptions;
}

const TuiWithForwardedRef: React.FC<TuiWithForwardedRefProps> = (props) => (
  <Editor
    {...props}
    ref={props.forwardedRef}
    usageStatistics={false}
    plugins={[
      [codeSyntaxHighlight, { highlighter: Prism }],
      [colorSyntax, props.colorSyntaxOptions],
      tableMergedCell,
    ]}
    // language={'ko-KR'}
  />
);

export default TuiWithForwardedRef;
Enter fullscreen mode Exit fullscreen mode

#2. The issue regarding alt text when using addImageBlockHook in hooks prop

There is a space for alt-text when uploading images.
description while uploading images
And here's a function that what you may think it's related to the "description"

type HookCallback = (url: string, text?: string) => void;

export type HookMap = {
  addImageBlobHook?: (blob: Blob | File, callback: HookCallback) => void;
};
Enter fullscreen mode Exit fullscreen mode

and the optional text prop sets the alt-text of the image you upload. However, the point is that "where can I get the description I typed in the description blank to assign it to the text prop.

So, how to fix it?

The answer is leave the text prop empty and the description that you put when uploading will be automatically assigned to the img element.

#3. Embed inside a component file

So the final ToastUI Editor component that you will embed on your component file may look like this:

const editorRef = React.useRef<EditorType>(); // ref hint

<ToastEditor
        forwardedRef={editorRef}
        initialEditType={'wysiwyg'}
        initialValue={'inital value is here'} // if you use placeholder then it causes an rendering error after pressing "insert code block" right after the editor is rendered
        height={'500px'}
        onChange={handleChange}
        hooks={{
          addImageBlobHook: async (fileOrBlob, callback) => {
            const uploadedImgURL = await uploadImg(fileOrBlob);
            callback(uploadedImgURL); // second argument is text which is optional, simply just ignore it
            console.log(uploadedImgURL);
          },
        }}
      />
Enter fullscreen mode Exit fullscreen mode

Package versions used in this post

"@toast-ui/react-editor": "^3.1.3",
"@toast-ui/editor-plugin-code-syntax-highlight": "^3.0.0",
"@toast-ui/editor-plugin-color-syntax": "^3.0.2",
"@toast-ui/editor-plugin-table-merged-cell": "^3.0.2",
"prismjs": "^1.27.0",
Enter fullscreen mode Exit fullscreen mode

Hope you find it helpful. Happy Coding!

TUI Editor Core
TUI Editor Github
PrismJS/prism
Joo Hee Kim's Medium post
yceffort blog - Korean

Discussion (0)