フックのルールに翻弄されないためのコンポーネント分割術(大袈裟)
投稿日: 2024年12月18日
コンポーネントは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();
これでフックのルールを担保した状態で条件に応じてデータを取得することが出来ます!! !!
具体例上がるとちょっと読みにくいと感じられる気はするのですが、結構このようにコンポーネントを分割することでエラーが解消できるようなパターンもあるのではないかと思います!
なんかルールに乗っ取って書くとエラー吐かれるけどどうしたら??って時には思い出していただけるといいかなと思います!!