【Next.js】状態フック「useEffect」と「useState」を組み合わせてDBからレコードを取得する

# クライアントクラスの非同期処理はuseEffectとuseStateで状態管理する

以前の記事にも書きましたが、クライアントクラス(use clientがファイルの冒頭についているクラス)に
非同期処理(asyncやawait)を直で記載すると「サポートしてないっ！」怒られてしまいます。↓

(怒られる例↓)
```app/auth/event/page.tsx
'use client'

export default async function Event(){
    //イベント情報を非同期で取得する処理
    await selectEvent(userid);
}
```

# 状態フック「useEffect」と「useState」を使おう！
クライアント側で非同期処理をするときは、状態フック「useEffect」と「useState」を併用して
使用するのが一般的です。

# サンプルを例に解説してみる

イベントの一覧影㎜から編集対象のイベントを選びます。（ここでは変種ボタンをクリックします）
![編集画面.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1169664/f3faeaff-0efe-cc96-0ddc-4f7ea88f3dcd.png)

編集画面（UIイメージはまだ完成していません...）のブラウザコンソールにデータベースから取得したレコードを出力しています。

![編集画面2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1169664/32cafde0-1438-9115-e06f-ab7d3aea568f.png)

このイメージをもとに編集画面側の非同期処理と状態管理のコードを解説します。
ブラウザに表示する側のクラスのコードは下記です。
【手順】
①編集対象イベント（targetEvent）の状態管理をuseStateフックを使って管理します。
```app/auth/eventEdit/page.tsx
const [targetEvent,settargetEvent] = useState<any>(null);
```

②useeffectを使って非同期処理を内部で実行します。userIdやeventIdが変更されるたびに処理が実行されます。
```app/auth/eventEdit/page.tsx
    useEffect(()=>{
        const fetchEvent = async ()=>{
            try{
                const event = await SelectEvent(Number(userId),Number(eventId));
                console.log("サーバから取得してきた編集対象イベントは、" + event);
                settargetEvent(event);
            }catch(error){
                console.error("イベントの取得中にエラーが発生しました：", error);
            }
        };
        fetchEvent();                
    },[userId,eventId]);
```

このイメージをもとに編集画面側の全体のコードを記載します。

```app/auth/eventEdit/page.tsx
'use client'

import { useRouter, useSearchParams } from "next/navigation"
import { useEffect,useState } from "react";
import SelectEvent from "@/app/pages/api/selectEvent";
//import { GETQueryParams } from "@/app/pages/api/urlQueryParameters";

export default function EventEdit(){
    //ルーティングの設定
    const router = useRouter();
    //クライアントコンポーネントフックであるuseSearchParamsを使用する
    const searchParams = useSearchParams();

    //URL Query Paramsからパラメータを取得する
    const userId = searchParams.get('userId');
    const eventId = searchParams.get('eventId');

    console.log("GETパラメータから受け取ったユーザーIDは、" + userId);
    console.log("GETパラメータから受け取ったイベントIDは、" + eventId);

    //ダッシュボードに戻るボタン押下時の処理
    const backToMyDashboard = ()=>{
        router.push("/auth/login/dashboard");
    }
    //状態でイベント情報を管理する
    const [targetEvent,settargetEvent] = useState<any>(null);

    useEffect(()=>{
        const fetchEvent = async ()=>{
            try{
                const event = await SelectEvent(Number(userId),Number(eventId));
                console.log("サーバから取得してきた編集対象イベントは、" + event);
                settargetEvent(event);
            }catch(error){
                console.error("イベントの取得中にエラーが発生しました：", error);
            }
        };
        fetchEvent();                
    },[userId,eventId]);

    console.log("編集対象のイベントは、" + JSON.stringify(targetEvent));

    return (
        <div>
            編集ページ
            <div className="eventEditArea">
                <button className="btn btn-primary">編集する</button>
                <button className="btn btn-accent">クリアする</button>
                <button className="btn btn-secondary" onClick={backToMyDashboard}>戻る</button>
            </div>
        </div>
    )
}
```

```app/pages/api/searchEvent.ts
'use server'

import { getDB } from "@/app/lib/db"

export default async function SelectEvent(userId:Number,eventId:Number){
    //データベースに接続する
    const connection = await getDB();
    //let targetEvent:any;
    try{
        const [event] = await connection.query(`SELECT * FROM eventinfo WHERE userid= ? AND eventid= ?`,[userId,eventId]);
        console.log(event);
        const targetEvent = [event][0]; //[event][0]
        console.log("取得したイベントは、" + targetEvent);
        console.log("取得したイベントは、" + JSON.stringify(targetEvent));
        return targetEvent;
    }catch(error){
        console.log(error);
    }finally{
        await connection.end();
    }
       
}
```

# ChatGPTからのuseEffectとuseStateの使い方の回答です↓
コードを見る限り、ブラウザのコンソールに期待するデータが出力されない原因は、以下のポイントにあると考えられます。

<b>非同期処理でのuseEffect内のconsole.logのタイミング:</b>

非同期処理内でawaitしている間、useEffect内部の後続コードがすぐに実行されるため、targetEventが非同期処理で更新される前の値をログ出力してしまっています。
targetEventがletで定義されているが、再レンダリングをトリガーしない:

targetEventは状態（state）ではなくローカル変数なので、値が更新されてもコンポーネントが再レンダリングされず、ブラウザには反映されません。
以下のように修正すれば、期待通りにデータがブラウザのコンソールに表示されるようになります。

<b>修正案</b>
useStateを使用して状態管理を行う:
非同期で取得したデータを状態として管理し、値が変更されるたびにコンポーネントを再レンダリングさせます。
非同期処理のエラーハンドリングを改善:
非同期関数内でエラーが発生しても適切に対応できるようにします。

サンプルコードは以下の通りです

```page.tsx
'use client';

import { useRouter, useSearchParams } from "next/navigation";
import { useEffect, useState } from "react";
import SelectEvent from "@/app/pages/api/selectEvent";

export default function EventEdit() {
    const router = useRouter();
    const searchParams = useSearchParams();

    const userId = searchParams.get('userId');
    const eventId = searchParams.get('eventId');

    console.log("GETパラメータから受け取ったユーザーIDは、" + userId);
    console.log("GETパラメータから受け取ったイベントIDは、" + eventId);

    const backToMyDashboard = () => {
        router.push("/auth/login/dashboard");
    };

    // 状態でイベント情報を管理
    const [targetEvent, setTargetEvent] = useState<any>(null);

    useEffect(() => {
        const fetchEvent = async () => {
            try {
                const event = await SelectEvent(Number(userId), Number(eventId));
                console.log("サーバから取得したイベントは、：" + JSON.stringify(event));
                setTargetEvent(event);
            } catch (error) {
                console.error("イベントの取得中にエラーが発生しました：", error);
            }
        };

        fetchEvent();
    }, [userId, eventId]);

    console.log("編集対象のイベントは、" + JSON.stringify(targetEvent));

    return (
        <div>
            編集ページ
            <div className="eventEditArea">
                <button className="btn btn-primary">編集する</button>
                <button className="btn btn-accent">クリアする</button>
                <button className="btn btn-secondary" onClick={backToMyDashboard}>戻る</button>
            </div>
            {targetEvent && (
                <div>
                    <h2>イベント情報</h2>
                    <pre>{JSON.stringify(targetEvent, null, 2)}</pre>
                </div>
            )}
        </div>
    );
}

```

<b>主な修正点</b>
<b>useStateの導入:</b>

targetEventをuseStateで管理することで、値の変更に応じて再レンダリングされるようにしました。
<b>■非同期処理のエラーハンドリング:</b>

<b>■try...catchを使用して、非同期処理中のエラーをキャッチしています。
useEffectの適切な使用:</b>

<b>■非同期処理を関数として分離し、useEffect内で呼び出しています。</b>

以上です。