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

Brandon Wie
4 min readApr 5, 2022

--

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

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(........

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;

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;

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;

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

There is a space for alt-text when uploading images.

And here’s a function that 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;
};

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 to 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);
},
}}
/>

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",

Hope you find it helpful. Happy Coding!

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

--

--

Brandon Wie

A front-end developer working at AIFFEL Modulabs in Seoul. Working as a DevOps in Google is my current goal.