2020-06-07に投稿

NextJSでマークダウンエディタを扱う際の注意点

SSR(サーバサイドレンダリング)の影響

draft.jsやtui-editorに共通して言えることですが、
元々ブラウザでの動作を想定して作られているので、
NextJSなどのSSR環境下ではwindowdocumentなどの変数の中身がundefinedになり、動作しなくなります。

Next.JSではSSRを無効にできる

With no SSR

インポート方法を変えることでSSRを無効にできるオプションがあります。
dynamic import自体はES2020で導入される構文だとか。

const DraftEditorNoSSR = dynamic<DraftEditorProp>(
  () => import('../components/DraftEditor') as any,
  { ssr: false }
)

Draft.jsを断念した理由

draft.jsでは再描画した際にカーソルが行の先頭に戻るというバグがあります。
公式リポジトリでは行の最後に移動する対処も議論されていますが、文章の中で編集したくなったらどうするんだ....という声もちらほら。

How to stop DraftJS cursor jumping to beginning of text?

さっさと見切りをつけてToastUI editorに移りました。
そうです、crieitにも採用されているエディタですね。

Toast UI Editorで実装できた!

ただし、Toast UI Editorはrefを使わないと値が取れず、
マウントされていない状態でrefがnullになってしまうことにかなり苦しめられました。

changeイベントがeditorで発火するのでフラグを更新するようにして、外側でrefを使ってデータを取り出すという実装になりました。
それから、画像アップロードをdisabledにしました。

editorコンポーネントを使いまわしたかったので、
表示用の初期値initialValueと値を更新して外部に渡すsetFuncをpropsとして与えています。

const DraftEditor = ({ initialValue, setFunc }) => {
  const editorRef = createRef<Editor>();
  const [changed, setChanged] = useState(false);

  useEffect(() => {
    if (editorRef.current && changed) {
      setFunc(editorRef.current.getInstance().getMarkdown());
      console.log("set");
      setChanged(false);
    }
  }, [editorRef.current, changed]);

  return (
    <div
      style={{
        width: "100vw",
      }}
    >
      <div>
        <Editor
          previewStyle="vertical"
          toolbarItems={[
            "heading",
            "bold",
            "italic",
            "strike",
            "divider",
            "hr",
            "quote",
            "ul",
            "ol",
            "task",
            "table",
            "link",
            "divider",
          ]}
          height="400px"
          initialEditType="markdown"
          initialValue={initialValue}
          ref={editorRef}
          hideModeSwitch={true}
          events={{
            change: (e) => {
              setChanged(true);
            },
          }}
        />
      </div>
    </div>
  );
};

export default DraftEditor;

export type DraftEditorProp = {
  initialValue: string;
  setFunc: Dispatch<any>;
};

ツイッターでシェア
みんなに共有、忘れないようにメモ

ckoshien

個人開発5年目。普段はフロントエンドエンジニア。 ReactJS/NextJS/NodeJS/ReactNative/Java

Crieitは誰でも投稿できるサービスです。 是非記事の投稿をお願いします。どんな軽い内容でも投稿できます。

また、「こんな記事が読みたいけど見つからない!」という方は是非記事投稿リクエストボードへ!

有料記事を販売できるようになりました!

こじんまりと作業ログやメモ、進捗を書き残しておきたい方はボード機能をご利用ください。
ボードとは?

コメント