【Next.js】Inputタグのvalue属性の扱い方について解説(備忘録)

# Inputタグのvalue属性の扱いは気をつけろ！
React.js(またはNext.js)で入力フォームといった`inputタグ`を使う機会は数多くあります。
その際に使われる属性の一つに<b>`value属性`</b>の扱いには注意が必要です。

# Inputタグに`value属性`を付けると値の更新ができない
普段使うInputタグは、入力制御可能なコンポーネントです。
しかし、React(Next.js)では、`属性`の使い方を間違えると、制御可能⇒制御不可能へとコンポーネントが変化してしまうという事象が起こてしまいます。

そんな例を下記の２つのサンプルコードを見てみましょう。

①`value属性`が<b>付いていない</b>Inputタグ
☞こちらは、値の更新ができます。
```page.tsx
<input type="text" name="sampleText" className="xxxxxx"/>
```
②`value属性`が<b>付いている</b>Inputタグ
☞これは値の<b>更新ができません。</b>
原因は、React.js(Next.js)側で制御不可能コンポーネントとして扱ってしまうからです。
```page.tsx
<input type="text" name="sampleText" value={value} className="xxxxxx"/>
```

# どんな場面で困るのか？事例を紹介
今回、イベント一覧画面から編集したいイベントを選んで編集画面で内容を更新するという事例を考えてみましょう。

<b>【画面のイメージフロー】</b>
①<b>イベント一覧画面</b>からリクエストパラメータ（ユーザーIDとイベントID）を遷移先の<b>編集画面</b>に送ります。
②リクエストパラメータを受け取った<b>編集画面</b>は、データベースから該当のレコードを1件取得します。
③取得した1件の結果を<b>編集画面</b>に表示します。

![Consolas.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1169664/38cf4972-34db-0996-2379-7fdb3b4f4853.png)

実際の画面差プルです。↓
このイベント一覧画面から編集対象のイベントを選択します。
![一覧.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1169664/93887191-aac9-72c4-337c-1c7b0c8fbe1e.png)

遷移先の<b>編集画面</b>データベースから値を取得して表示します。
![編集画面1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1169664/e37b8f98-b911-5a77-9c44-ca482cfbff89.png)

# 何も考えずInputの`value`属性をつけると入力不可
データベースから取得したデータを画面に表示するためInputタグに`valuse属性`を付けてしまうと残念なことに入力ができなくなってしまいます。↓

```app/auth/eventEdit/page.tsx
///何らかの処理
    return (
        <div className="w-full w-96 content-center">
            <div>
                編集画面
            </div>
            <form action="/api/eventEdit" method="POST" className="bg-white shadow-md rounded px-8 pt-6 pb-8 mb-4">
                <div className="userIdArea">
                    <input type="hidden" name="userId" value={targetEvent.userid || ''}/>                   
                </div>
                <div className="eventIdArea">
                    <input type="hidden" name="eventId" value={targetEvent.eventid || ''}/>
                </div>
                <div className="eventTitleArea">
                    <input
                        type="text"
                        name="eventTitle" 
                        value={targetEvent.eventtitle}
                        className="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight"                                           
                    />
                    
                </div>
                <div className="eventDetailArea">
                    <textarea
                        name="eventDetail"
                        value={targetEvent.eventdetail}
                        className="block p-2.5 w-full text-sm text^gray-900 border border-gray-300"
                    ></textarea>
                </div>
                <div className="eventThumbnailArea">
                    <input type="file" 
                        name="eventImage" 
                        value={targetEvent.eventthumbnailpath}
                        className="file-input file-input-bordered file-input-primary w-full"
                        />
                </div>
            </form>
            <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>
    )
```

これでは、イベント情報が全く更新できませんね...
では、どうしたらよいでしょうか...？

# `value`ではなく`defaultValue属性`を付ける
もっともシンプルな方法は、`defaultValue属性`を付けることです。
使い方はカンタンで、さっきの`value属性`を`defaultValue属性に変えてあげるだけです。`
<b>【React公式サイト】</b>
![React_input.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1169664/1feb1332-f604-36f0-45fd-d1ed02ea9161.png)

https://ja.react.dev/reference/react-dom/components/input#providing-an-initial-value-for-an-input




```app/auth/eventEdit/page.tsx
<div className="eventTitleArea">
    <input
        type="text"
        name="eventTitle" 
        value={targetEvent.eventtitle}
        className="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight"                                           
    />
    
</div>
```

以上を踏まえた全体のソースコードはこちらです↓
```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();
    //const [targetUserId,settargetUserId] = useState(0);

    //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[]>([]);//useState<any>(null);
    
    //let event:any;
    useEffect(()=>{
        const fetchEvent = async ()=>{
            try{
                console.log("内部のユーザーIDは、" +userId);
                const event = await SelectEvent(Number(userId),Number(eventId));
                console.log("サーバから取得してきた編集対象イベントは、" + event[0]);
                console.log("サーバから取得してきた編集対象イベントは、" + event[0].userid);
                settargetEvent(event[0]);

                console.log("useeffectの編集対象のイベントは、" + JSON.stringify(event[0]));                                                                                                                            
            }catch(error){
                console.error("イベントの取得中にエラーが発生しました：", error);
            }
        };
        fetchEvent();                
    },[userId,eventId]);//[userId,eventId]

    let jsonEvent = JSON.stringify(targetEvent);
    console.log("外の編集対象のイベントは、" + jsonEvent);
    console.log("useEffect外の編集対象のユーザIDは、" + targetEvent.userid);
    console.log("useEffect外の編集対象のイベントタイトルは、" + targetEvent.eventtitle);            

    console.log("useEffect外の編集対象のイベントタイトル(targetEventtitle)は、" + targetEvent.eventtitle);
                      
    return (
        <div className="w-full w-96 content-center">
            <div>
                編集画面
            </div>
            <form action="/api/eventEdit" method="POST" className="bg-white shadow-md rounded px-8 pt-6 pb-8 mb-4">
                <div className="userIdArea">
                    <input type="hidden" name="userId" value={targetEvent.userid || ''}/>                   
                </div>
                <div className="eventIdArea">
                    <input type="hidden" name="eventId" value={targetEvent.eventid || ''}/>
                </div>
                <div className="eventTitleArea">
                    <input
                        type="text"
                        name="eventTitle" 
                        defaultValue={targetEvent.eventtitle}
                        className="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight"                                           
                    />
                    
                </div>
                <div className="eventDetailArea">
                    <textarea
                        name="eventDetail"
                        defaultValue={targetEvent.eventdetail}
                        className="block p-2.5 w-full text-sm text^gray-900 border border-gray-300"
                    ></textarea>
                </div>
                <div className="eventThumbnailArea">
                    <input type="file" 
                        name="eventImage" 
                        defaultValue={targetEvent.eventthumbnailpath}
                        className="file-input file-input-bordered file-input-primary w-full"
                        />
                </div>
            </form>
            <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>
    )
}
```

サーバ側で対象のイベント情報を1件取得するコードはこちらです↓
```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();
    }
       
}
```

以上です。






# スペシャルサンクス

https://scrawledtechblog.com/react-input/

https://qiita.com/koyablue/items/e9f4a04b417e7174a915