Torihaji's Growth Diary

Little by little, no hurry.

Nextjs で Editorjsを使ってみた part1

はじめに

みなさん、こんにちは torihaziです

開発実務初めて1ヶ月が経ち、毎日ガリガリ書いてます

今日は実装するのに1週間苦しんだEditorjsを

少し触ってみて魔改造とは言わないまでも

色々できるようになるまでいじり倒してみたいと思います。

ちなみに実務の方では、

エディタの中身をbackendにsubmitしたり、それをgetしてエディタに読み込ませたものを表示したり

編集したりというところまではできるようになりました。

ただ現時点、あんまり理解できていません。

あと日本語のドキュメントがあまりに少なすぎます。

これを書き切ってまとめられるようになったらqiitaにでもあげたいと思います。

使用するもの

Docker使ってNextjsの環境構築を行います。

https://torihazi.hateblo.jp/entry/2024/09/01/154108

ここの環境構築欄に書いてあるのでよかったら使ってください

入れてみる。

最初はまずものを入れてみるところから。

editorjs.io

npm i @editorjs/editorjs --save

~/features/editor/editos.ts作ってその中に

import EditorJS from "@editorjs/editorjs";

const Editor = () => {
  const editor = new EditorJS();

  return <div id="editorjs"></div>;
};

export default Editor;

と書いてみる。

で。

この後どうするの。

実務では特に理由もわからず、useEffect使って

その中でインスタンス化して、ref使って、・・・ってしてたけど。

なんでそもそもuseEffectとかって書くのだろうか。

react-editor-jsという非公式の物があるので、ヒントを得に覗いてみる。

react-editor-js - npm

よくわからなかったので、とりあえず書いてみた。

ちなみに書いたところはpages/edit.tsxというところ。

import EditorJS from "@editorjs/editorjs";

const Editor = () => {
  const editor = new EditorJS();

  return <div id="editorjs"></div>;
};

export default Editor;

そうするとエラーが。

ReferenceError: Element is not defined

Claudeに聞いてみると

Element が定義されていないというエラーは、ブラウザ環境で通常利用可能な Element インターフェースが見つからないことを示しています。 これは、このコードがサーバーサイドで実行されようとしていることを示唆しています。 サーバー環境には Element や document などのブラウザ API が存在しません。 Next.js のようなフレームワークを使用している場合、デフォルトでページコンポーネントはサーバーサイドレンダリングSSR)されます。 EditorJS はクライアントサイドのライブラリであり、ブラウザ環境を前提としています。サーバーサイドで実行しようとすると、このようなエラーが発生します。

なるほど。じゃあそのクライアントの方でやればいいのでは。

https://qiita.com/TaikiTkwkbysh/items/3a353b306b2eabd5205e

ということでuseEffectで囲んだ。

import EditorJS from "@editorjs/editorjs";
import { useEffect } from "react";

const Editor = () => {
  useEffect(() => {
    const editor = new EditorJS();
  }, []);

  return <div id="editorjs"></div>;
};

export default Editor;

結果変わらず。

ただこうするとエディタが表示された。

import { useEffect } from "react";

const Edit = () => {
  useEffect(() => {
    import("@editorjs/editorjs").then((EditorJS) => {
      const editor = new EditorJS.default();
    });
  }, []);

  return <div id="editorjs"></div>;
};

export default Edit;

見えにくいが、2つエディタが表示されている。

エディタが2つ表示される原因

※ちなみにエディタが2つ表示される原因はStrictmode : trueだかららしい。

つまり開発環境が故に起こるもの。

確かにnext.config.mjsにおいてreactStrictModeをfalseにしたら消えた。

https://zenn.dev/nicopin/articles/3bbeb6f31347ef

さっきあげた成功コードの何がアレなのかよくわからないので

自分なりに調べてみた。

変わったこととしては3つ

  • useEffectを使った
  • モジュールを動的importした

1つ目、useEffectについて。

これはEditorjsのようなクライアントサイドでしか動かないライブラリは

クライアントサイドで動かす必要があり、それを可能にするのがuseEffectだから。

あとはuseEffectを使うとDOM要素が描画されたあとにインスタンス化できるからだ。

どういうことかというと、

Editorjsはインスタンス化するときにholderに設定したidを持つDOM要素を

探しに行って存在確認をしているらしい。

Githubソースコードを遡ると書いてある。

https://github.com/codex-team/editor.js/blob/next/src/codex.ts#L24

const editor = new Core(configuration);

のなかで呼んでいる

https://github.com/codex-team/editor.js/blob/next/src/components/core.ts#L1

this.validate();だ。

このvalidateが中で$.get(holderId)たるものを実行し

このget自体がjavascriptでお馴染みのgetElementByIdを実行している。

もちろんそのvalidateで発行しているエラー文が出てきてないので

断言はできないが、原因の1つではあると思う。

2つ目の動的インポートについて。

これは正直よくわからなかったのでClaudeに聞いた。

最初の方法、いつものように最上部でimportする静的importは

コンポーネントレンダリング前に行われる

=> SSRではサーバ上で行われる可能性がある

=> Editorjsはクライアントでしか動かないからエラーになった

そしてuseEffectはコンポーネントのマウントが終わったあと、

つまりクライアントサイドで行われ、そのタイミングでimportするには

動的importしか方法がないのでこう書いた、というもの。

まぁ確かに論理は通っている気がするのでそういうものだと理解することにした。

ただ調べてみるとrequireでも動作した。

import { useEffect } from "react";

const Edit = () => {
  useEffect(() => {
    // import("@editorjs/editorjs").then((EditorJS) => {
    //   const editor = new EditorJS.default();
    // });
    const EditorJS = require("@editorjs/editorjs");
    const editor = new EditorJS.default();
  }, []);

  return <div id="editorjs"></div>;
};

export default Edit;

nextjsのdynamicという方法もあるみたいだが、うまく動かなかった。

なぜだろう。

まあいいか。一旦。

とりあえず表示できたし。

終わりに

だからuseEffectを使うのか。

クライアントサイドだから、サーバサイドだからとかが関係していたんですね。

あとuseEffectの本来の挙動とかあんまり理解していなかったので

良い勉強になりました。

とりあえず自分なりに導入してみてわかったことは3つ。

  • Editorjsはクライアントサイドでしか動かないしインスタンス化時点でholderに設定したidを持つDOMが描画されている必要があるから、それを満足するuseEffect使え
  • useEffectに伴い、動的importも必須
  • 重複で描画されるのは StrictModeが原因

ということ。

てか実務でuseEffectのなかでtypeof window !== undefinedの分岐を入れてたけど

今日のことからこれは確実にtrueなのか。あーあ。まぁいいか。

なんでref使うのかとかはまた明日。