fetchを使ったAPI通信(Next.js)その④

Next.js-3.API通信編-

データ更新(Update)

app/components/UpdateDog.js

"use client";
import { useState } from "react";

export default function UpdateDog(props) {
    const [updatedValues, setUpdatedValues] = useState({}); // 名前&年齢の変更用ステート

    // ✅ 名前・年齢の入力変更を管理する関数
    const handleInputChange = (id, field, value) => {
        setUpdatedValues({
            ...updatedValues,
            [id]: { ...updatedValues[id], [field]: value }
        });
    };

    // ✅ 名前&年齢をまとめて更新
    const updateDogInfo = async (id) => {
        const currentDog = props.dogs.find((dog) => dog.id === id);
        const newData = {
            name: updatedValues[id]?.name ?? currentDog.name, // 入力がなければ元の値
            age: updatedValues[id]?.age ? Number(updatedValues[id].age) : currentDog.age // 入力がなければ元の値
        };

        const response = await fetch(`http://localhost:3001/dogs/${id}`, {
            method: "PUT",
            headers: { "Content-Type": "application/json" },
            body: JSON.stringify(newData)
        });

        if (response.ok) {
            props.setDogs(
                props.dogs.map((dog) => (dog.id === id ? { ...dog, ...newData } : dog))
            );
        }
    };

    return (
        <div>
            <h2>🐕 犬の情報を更新</h2>
            {props.dogs.map((dog) => (
                <div key={dog.id}>
                    {/* 名前の入力欄 */}
                    <input
                        type="text"
                        value={updatedValues[dog.id]?.name ?? dog.name}
                        onChange={(e) => handleInputChange(dog.id, "name", e.target.value)}
                    />

                    {/* 年齢の入力欄 */}
                    <input
                        type="number"
                        value={updatedValues[dog.id]?.age ?? dog.age}
                        onChange={(e) => handleInputChange(dog.id, "age", e.target.value)}
                    />

                    {/* 更新ボタン(1回の通信で名前&年齢を変更) */}
                    <button onClick={() => updateDogInfo(dog.id)}>更新</button>
                </div>
            ))}
        </div>
    );
}

コード解説

state設定

 const [updatedValues, setUpdatedValues] = useState({}); 

犬の名前と年齢の変更を一時的に保存するための状態管理のためのstate

実際の updatedValues の中身(例)

{
    "1": { "name": "ゴールデンレトリバー", "age": 5 },
    "2": { "name": "柴犬", "age": 3 }
}

📌 このように id をキーとして、nameage を管理

handleInputChange(名前・年齢の入力変更を管理する関数)

const handleInputChange = (id, field, value) => {
    setUpdatedValues({
        ...updatedValues,
        [id]: { ...updatedValues[id], [field]: value }
    });
};

ユーザーが入力した値を updatedValues に保存
スプレッド構文 ...updatedValues で、元の updatedValues の内容を維持
新しい id のデータを { ...updatedValues[id], [field]: value } でスプレッド構文を使って更新

updateDogInfo(API通信する関数)

const updateDogInfo = async (id) => {

「id」を引数とする「更新」ボタンをクリックしたときのイベントハンドラでAPI通信します。

currentDog に id に一致する犬の情報を取得

const currentDog = props.dogs.find((dog) => dog.id === id);
find関数の説明

親コンポーネントから渡ってきたprops.dogsの中で「id」が合致する犬の要素を抽出しています。

更新するデータ newData を作成

const newData = {
    name: updatedValues[id]?.name ?? currentDog.name,
    age: updatedValues[id]?.age ? Number(updatedValues[id].age) : currentDog.age
};
name: updatedValues[id]?.name ?? currentDog.name

?.(オプショナルチェーン)を使い、入力がない場合は undefinedを返すように。
??(Nullish Coalescing)を使い、 undefined のときに初期値(currentDog.name)を設定している

 age: updatedValues[id]?.age ? Number(updatedValues[id].age) : currentDog.age

?.(オプショナルチェーン)を使い、入力がない場合は undefinedを返すように。

? : 三項演算子を使い、 undefined のときに初期値(currentDog.age)を設定

PUT でサーバーに送信

const response = await fetch(`http://localhost:3001/dogs/${id}`, {
    method: "PUT",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify(newData)
});

fetchjson-server にデータを送る
method: "PUT" により、${id}で指定したデータの既存データを上書き
JSON.stringify(newData)newDataJSON 形式に変換

ここの処理はjson-server規定の動きに準じています。

実際はバックエンドアプリのルーティングに沿ってアクセスしたURLごとに処理が動き、その返り値が返ってくることになります。

props.setDogs で state を更新

if (response.ok) {
    props.setDogs(
        props.dogs.map((dog) => (dog.id === id ? { ...dog, ...newData } : dog))
    );
}

response.okがtrueだったときに、props.setDogsの更新用関数を使ってprops.dogsを更新しています。

mapの処理で三項演算子を使い、指定idの犬の場合は内容を更新し、それ以外はもともとの犬の入った新しいdogsを作り、setDogsで更新しています。

例えばスプレッド構文を使わずに下記のように書くと同じようにnameとageとキーの値を更新できます。

 { ...dog, "name": "柴犬", "age": 5}

JSX

最後に戻り値として返すJSXです。

return (
        <div>
            <h2>🐕 犬の情報を更新</h2>
            {props.dogs.map((dog) => (
                <div key={dog.id}>
                    {/* 名前の入力欄 */}
                    <input
                        type="text"
                        value={updatedValues[dog.id]?.name ?? dog.name}
                        onChange={(e) => handleInputChange(dog.id, "name", e.target.value)}
                    />

                    {/* 年齢の入力欄 */}
                    <input
                        type="number"
                        value={updatedValues[dog.id]?.age ?? dog.age}
                        onChange={(e) => handleInputChange(dog.id, "age", e.target.value)}
                    />

                    {/* 更新ボタン(1回の通信で名前&年齢を変更) */}
                    <button onClick={() => updateDogInfo(dog.id)}>更新</button>
                </div>
            ))}
        </div>
    );

mapメソッド

{props.dogs.map((dog) => (

props.dogsをmapで処理しそれぞれの入力用リストを作成します。

props.dogs のリストを map() で1匹ずつ取り出す
dog に1匹ずつデータが入る

リストのタグ

<div key={dog.id}>

<div> で各犬の情報を囲む!
key={dog.id} を入れることで、React がどの犬かわかるようにする(リストの必須ルール)

名前の入力欄(<input>)を作る

<input
    type="text"
    value={updatedValues[dog.id]?.name ?? dog.name}
    onChange={(e) => handleInputChange(dog.id, "name", e.target.value)}
/>

名前を入力するための <input>(テキストボックス)を作成

属性意味
type=”text”文字を入力できるテキストボックス
value={updatedValues[dog.id]?.name ?? dog.name}入力値の管理(もし updatedValues に新しい名前があればそれを表示、なければ dog.name を表示)
onChange={(e) => handleInputChange(dog.id, “name”, e.target.value)}入力が変わったら handleInputChange() を実行して、updatedValues に保存

ここでも「?」オプショナルチェーンと「??」Nullish Coalescingを使ってupdateValuesの値がない場合は元々の値を初期値として表示されるようにしています。

年齢の入力欄(<input>)を作る

同じく年齢ようの入力欄も作ります。

<input
    type="number"
    value={updatedValues[dog.id]?.age ?? dog.age}
    onChange={(e) => handleInputChange(dog.id, "age", e.target.value)}
/>

名前と同じように、年齢も入力できる

属性意味
type=”number”数字だけ入力できるボックス
value={updatedValues[dog.id]?.age ?? dog.age}新しく入力された age があればそれを表示、なければ dog.age を表示
onChange={(e) => handleInputChange(dog.id, “age”, e.target.value)}入力が変わったら handleInputChange() を実行して、updatedValues に保存

更新ボタンを作る

<button onClick={() => updateDogInfo(dog.id)}>更新</button>

「更新」ボタンを押すと updateDogInfo(dog.id) が実行される
これで PUT リクエストが送られ、サーバーのデータが更新される

まとめ

以上がデータ更新のコンポーネントでした。色々な書き方が出てきて複雑に感じるかと思いますが、まずは何をやっているのか理解できるように咀嚼してみてください。

コメント

タイトルとURLをコピーしました