Implement ToastUI Editor with Next.JS (w/ TypeScript)
Tags: toastui, editor, typescript, nextjs
Published: 2022-04-05 10:25:03

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](https://ui.toast.com/tui-editor) inside Next.JS projects.

## #1.ReferenceError: self is undefined
![self is undefined error](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dwh6nohkgkp5ftt8dtz4.png)
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.

```javascript
// 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__)
```
In short, the `self` at the bottom left of the code above causes the error. ToastUI needs `window` object to initiate
```typescript
// 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:
```typescript
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.

```typescript
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.
![description while uploading images](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/n9crzykx2z65w6ld6ad2.png)
And here's a function that what you may think it's related to the "description"  
```typescript
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 **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:
```typescript
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
```json
"@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](https://nhn.github.io/tui.editor/latest/ToastUIEditorCore)
[TUI Editor Github](https://github.com/nhn/tui.editor/tree/master/plugins/code-syntax-highlight)
[PrismJS/prism](https://github.com/PrismJS/prism)
[Joo Hee Kim's Medium post](https://paigekim29.medium.com/toast-ui-editor-next-js-c9b48927fbf7)
[yceffort blog - Korean](https://yceffort.kr/2021/03/server-side-rendering-and-react-components)