🐙

reactStrictModeでのuseEffectと非同期処理

2022/09/26に公開

止むに止まれぬ事情で、 useEffect内で非同期処理をしているのですが、その際に、思った挙動にならなかった部分があり原因がわかったので備忘録として記録する。

目的

https://editorjs.io/
EditorJSのラッパーコンポーネントを作成している過程で、クリーンアップしているのにエディターが重複描画されることがあった。
当時の構成は下記のような形

  const uuid = useId();
  const { locale } = useLocale();
  const editorjs = useRef<EditorJS | null>(null);
  const setup = async () => {
    const e = new EditorJS({
      placeholder,
      readOnly,
      minHeight,
      autofocus,
      hideToolbar,
      holder: uuid,
      data: defaultValue,
      i18n: i18n(locale),
      tools: EditorTools(locale),
      onChange(api: API, event: CustomEvent) {
        if (readOnly) return;
        editorjs.current?.save().then((res) => {
          if (onSave) {
            onSave(res);
          }
        });
        if (onChange) {
          onChange(api, event);
        }
      },
      onReady() {
        if (onReady) {
          onReady();
        }
      },
    });
    await e.isReady;
    editorjs.current = e;
  };
  const cleanup = async () => {
    await editorjs.current?.isReady;
    editorjs.current?.destroy();
  };
  useEffect(() => {
    setup();
    return () => {
      cleanup();
    };
  }, []);

調査結果

非同期処理の実行タイミングの問題なのかな〜と思い下記のサンプルコードで検証したところ、同じように重複描画されることを確認。

  useEffect(() => {
    setTimeout(() => {
      const element = document.getElementById("test");
      if (element) {
        const child = document.createElement("div");
        const childText = document.createTextNode("new text line");
        child.appendChild(childText);
        element.appendChild(child);
      }
    }, 2000);
    return () => {
      setTimeout(() => {
        const element = document.getElementById("test");
        if (element) {
          while (element.firstChild) {
            element.removeChild(element.firstChild);
          }
        }
      }, 1000);
    };
  }, []);
  return  <div id="test" />

strictModeの場合には、effect->cleanup->effectが実行されるので、effectとcleanupが非同期であると、必ずしもcleanupが期待した通りに初回のeffectの内容をリセットできるわけではない。

対応方針

https://beta.reactjs.org/learn/synchronizing-with-effects#how-to-handle-the-effect-firing-twice-in-development

これによればUIに影響があるのであれば、適宜修正を加えるべしと書いてあるけれど、この事象が起きるのはstrictModeの時だけで現状あまり綺麗に書ける実装が思いつかないので、本機能開発においては一時的にstrictModeを外して対応することにした。
もしこのような事象に対応された方がいらっしゃればぜひご共有いただきたい。

補足

今回はどうしてeditorjsを使いたくて上記のような実装になったが、そもそも、useEffectのなかで非同期処理はしない方が良さそう。

Discussion