🦅

アンマウント時のステート更新エラーへの対処

2022/07/15に公開

はじめまして。
プログラミング勉強中のあかしと言います。
初めて記事を書くので、至らない所が多々あるかと思いますが、
良かったら読んだやってください。


環境

記事の内容とは直接関係無いものもありますが、以下の様な環境です。

package.json
  "dependencies": {
    "@chakra-ui/icons": "^1.0.4",
    "@chakra-ui/react": "^1.3.0",
    "@emotion/react": "^11",
    "@emotion/styled": "^11",
    "firebase": "^8.10.0",
    "framer-motion": "^6",
    "next": "12.1.6",
    "npm": "^8.13.2",
    "react": "17.0.2",
    "react-confetti": "^6.1.0",
    "react-dom": "17.0.2",
    "react-hook-form": "^7.33.1",
    "react-icons": "^4.4.0",
    "react-use": "^17.4.0",
    "recoil": "^0.7.4",
    "recoil-persist": "^4.2.0"
  },
  "devDependencies": {
    "@types/node": "18.0.0",
    "@types/react": "17.0.2",
    "@types/react-dom": "17.0.2",
    "eslint": "8.18.0",
    "eslint-config-next": "12.1.6",
    "typescript": "4.7.4"
  }

開発中のアプリについて

ヘッダーが認証状態において、以下の2パターンに分かれます。

  • ログイン時:ユーザーのアバター、ログアウトのアイコン

  • ログアウト時:新規登録のアイコン、サインインのアイコン

エラー内容

ページを遷移する際に、以下のエラーが出ました。

要は「アンマウントされたのに、ステートの更新はできないよ」と言っています。
どうやら原因はログイン時にユーザーのアバター画像をステートで更新していたことでした。

エラーへの対処

async/awaitだったり、useEffectだったり色々と試してみたのですが、
アプリの状況に合うのは以下の記事の内容でした。
https://bobbyhadz.com/blog/react-cant-perform-react-state-update-on-unmounted-component

対処方法は、マウント状態を監視する変数を用意し、変数がtrue(マウントしている)の時のみステートの更新を行う様にします。
様々なコンポーネントで再利用したかったので、カスタムフックを用意しました。

useIsMounted.ts
import { useEffect, useRef } from "react";

export default function useIsMounted() {
  // isMountedにはマウント状態をbooleanで管理
  const isMounted = useRef(false);
  useEffect(() => {
    // マウント時はtrue
    isMounted.current = true;
    return () => {
      // アンマウント時はfalse
      isMounted.current = false;
    };
  });
  return isMounted;
}

そして、アンマウント時にステートを更新していた部分に、
isMountedを用いてマウントしている時のみステートを更新する様に変更しました。

useIsMounted.ts
// 用意したカスタムフックをインポート
import useIsMounted from "../hooks/useIsMounted";

  const [avatarUrl, setAvatarUrl] = useState<any>("");
  // マウント状態を
  const isMountedRef = useIsMounted();
  const docRef = db.collection("users").doc(uid);
  docRef
    .get()
    .then((doc) => {
      if (doc.exists) {
        // マウント時のみステートを更新
        if (isMountedRef.current) setAvatarUrl(doc.data()?.imageUrl);
      } else {
        alert("No such document!");
      }
    })
    .catch((error) => {
      alert(error);
    });

これで、エラーが出なくなりました。

最後に

拙い知識で書いた記事を最後まで読んでいただき、ありがとうございました。
間違い等ありましたら、教えていただけると大変ありがたいです。

Discussion