フックのルールに翻弄されないためのコンポーネント分割術(大袈裟)

フックのルールに翻弄されないためのコンポーネント分割術(大袈裟)

投稿日: 2024年12月18日

学習振り返り
Tips
要約
  • Reactのフックを使用する際には、条件分岐の中で呼び出せない制約があり、コンポーネントを分割することが有効である。
  • コンポーネントを分けることで、ユーザーの登録処理の完了を待ってからデータを取得できるようにし、エラーを回避した例を示した。
  • ルールに従ってコーディングすることでエラーが発生する場合には、コンポーネント分割を考慮することが有効である。

はじめに

コンポーネントは2回以上同じパーツが使われる場合に作るものというのが原則だと思いますが、現状一回しか使わないけどコンポーネント分けないと上手くいかないということがあります。

今日コーディングする中でそのパターンと出会ったのでこんなこともあるよと情報共有していきたいなと思います!

ザックリ説明

フックを使う時、そのコンポーネントやフックのトップレベルの箇所でしか使えないという縛りがありますよね?

関数の中や条件式の処理の中、早期リターン後にはフック使えないですよね。

例えば、

const { token, isLoading } = useSupabaseSession();
if(token) {
  const { data, error } = useCourses();
}

これ出来ないですよね。

このルールを守ろうとすると、上手くいかないことがありました。

分ける前

これはうまくいかないパターンです。

"use client";
import Link from "next/link";
import { useCourses } from "../_hooks/useCourses";
import { language } from "../_utils/language";
import { api } from "../_utils/api";
import { useEffect } from "react";
import { useSupabaseSession } from "../_hooks/useSupabaseSessoin";
export default function Courses() {
  const { data, error } = useCourses();
  const { token, isLoading } = useSupabaseSession();
  useEffect(() => {
    const postUser = async () => {
      if (!token) return;
      try {
        const resp = await api.post<{}, { message: string }>(
          "/api/create_user",
          {}
        );
      } catch (err) {
        console.error("ユーザー作成API呼び出し中にエラー:", err);
      }
    };
    postUser();
  }, []);
  if (isLoading) return <div className="text-center">読込み中...</div>;
  if (!data) return <div className="text-center">読込み中...</div>;
  if (error)
    return (
      <div className="text-center">コースの取得中にエラーが発生しました</div>
    );
  if (data.courses.length === 0)
    return <div className="text-center">コースがありません</div>;

  return (
    <div className="">
      <h2 className="p-10 text-5xl">Course一覧</h2>
      <div className="flex flex-col gap-10 p-10">
        {data.courses.map(course => (
          <Link
            href={`/courses/${course.id}`}
            key={course.id}
            className=" shadow-md"
          >
            <div className="p-5">{language(course.name)}</div>
          </Link>
        ))}
      </div>
    </div>
  );
}

ザっと処理の説明ですが、このページは初回レンダリング時にtokenがあったらPOSTリクエストを行います。

このエンドポイントではユーザー情報の存在確認を行い、ユーザーのDB登録を行うか、既存のユーザーなら何も処理せずにstatus:200を返します。

なぜうまくいかないかというと、

const { data, error } = useCourses();

これがユーザー登録が未済の場合、先に処理が走ってしまい、認証エラーを返してしまうという問題があります。

ユーザー登録出来てたらコースデータの取得をしたい、出来てなかったら処理したくない!という場面です。

この問題をコンポーネントを分割することで解消します。

分けた後

"use client";
import { api } from "../_utils/api";
import { useEffect, useState } from "react";
import { useSupabaseSession } from "../_hooks/useSupabaseSessoin";
import { CoursesList } from "./_components/CoursesList";
export default function Courses() {
  const { token, isLoading } = useSupabaseSession();
  const [isUserCreated, setIsUserCreated] = useState(false);
  useEffect(() => {
    const postUser = async () => {
      if (isLoading) return;
      if (!token) return;
      try {
        const resp = await api.post<{}, { message: string }>(
          "/api/create_user",
          {}
        );
        setIsUserCreated(true);
      } catch (err) {
        console.error("ユーザー作成API呼び出し中にエラー:", err);
      }
    };
    postUser();
  }, [token, isLoading]);
  return (
    <>
      <h2 className="p-10 text-5xl">Course一覧</h2>
      {!isUserCreated ? (
        <div className="text-center">読込み中...</div>
      ) : (
        <CoursesList />
      )}
    </>
  );
}

"use client";
import Link from "next/link";
import { useCourses } from "@/app/_hooks/useCourses";
import { language } from "@/app/_utils/language";
export const CoursesList: React.FC = () => {
  const { data, error } = useCourses();
  if (!data) return <div className="text-center">読込み中...</div>;
  if (error)
    return (
      <div className="text-center">コースの取得中にエラーが発生しました</div>
    );
  if (data.courses.length === 0)
    return <div className="text-center">コースがありません</div>;

  return (
    <div className="flex flex-col gap-10 p-10">
      {data.courses.map(course => (
        <Link
          href={`/courses/${course.id}`}
          key={course.id}
          className=" shadow-md"
        >
          <div className="p-5">{language(course.name)}</div>
        </Link>
      ))}
    </div>
  );
};

下記の処理を行うコンポーネントをレンダリングするタイミングを親コンポーネントで制御します。

const { data, error } = useCourses();

これでフックのルールを担保した状態で条件に応じてデータを取得することが出来ます!! !!

おわりに

具体例上がるとちょっと読みにくいと感じられる気はするのですが、結構このようにコンポーネントを分割することでエラーが解消できるようなパターンもあるのではないかと思います!

なんかルールに乗っ取って書くとエラー吐かれるけどどうしたら??って時には思い出していただけるといいかなと思います!!

シェア!

Threads
user
吉本茜
山口在住/二児の母/育休中
記事一覧に戻る
Threads
0