ブログ

  • Hello world!

    WordPress へようこそ。こちらは最初の投稿です。編集または削除し、コンテンツ作成を始めてください。

  • 【MW WPForm終了対策】Contact Form 7で完了ページにリダイレクトする方法【公式推奨】

    MW WPFormの開発終了に伴い、フォームの再構築を余儀なくされている制作現場が増えています。
    特に多いのが、Contact Form 7(以下CF7)に移行したものの、

    • 完了ページへ遷移できない
    • JavaScriptで無理やり対応している
    • 有料アドオンに頼るしかない?

    というケース。

    ですが、実はCF7公式が紹介している方法で、無料かつ簡潔に完了ページ遷移が可能なんです。


    結論:JavaScriptとfunctions.phpで完了ページ遷移はできる

    公式ドキュメント(CF7公式リンク)では、次のようなJavaScriptイベントを使う方法が紹介されています。

    document.addEventListener( 'wpcf7mailsent', function( event ) {
    location.href = '/thanks/';
    }, false );

    ただし、footer.phpなどに直書きするのではなく、WordPressのfunctions.phpに記述して管理するのがベストです。


    実装手順(functions.phpで対応)

    以下のコードを、子テーマのfunctions.phpファイルに追記してください。

    add_action( 'wp_footer', function() {
    ?>
    <script>
    document.addEventListener( 'wpcf7mailsent', function( event ) {
    location.href = '/thanks/'; // 任意の完了ページURLに変更
    }, false );
    </script>
    <?php
    });

    ポイント解説

    • wpcf7mailsent:送信成功時に発火する公式イベント
    • location.href = '/thanks/';:送信後の遷移先
    • wp_footer:WordPressのフッター領域にスクリプトを挿入するフック

    注意点

    • JavaScriptが無効な環境では動作しません(稀)
    • 完了ページ /thanks/ は事前に作成しておく必要があります
    • 複数フォームがある場合は event.detail.contactFormId を条件に含めて制御できます

    例:

    if (event.detail.contactFormId == '123') {
    location.href = '/thanks/';
    }

    補足:Contact Form 7 で完了ページを使うメリット

    • GAやCVタグの設置が可能(完了ページでトラッキングしやすい)
    • 送信後に安心感を与える導線が作れる
    • 静的なthanks.htmlなどにも遷移できる

    まとめ

    MW WPForm終了後、CF7への移行は王道ですが「完了ページが出せない」と悩む必要はありません。

    公式の方法を活用すれば、プラグイン不要・コードのみで対応可能です。


    関連リンク


    お困りの方へ

    制作会社様や担当者の方で「実装が不安」「ついでにフォーム全体を整理したい」という方は、お気軽に[お問い合わせページ]からご相談ください。

  • Laravel(バックエンド) + Next.js(フロントエンド)の構成例

    API通信編ではjson-serverにてバックエンドを構築しましたが、より実践的にバックエンドをLaravelを用いた場合の構成例と各種設定について説明します。

    結論 → Laravel を API サーバーとして使い、Next.js でフロントエンドを構築する!
    データのやり取りは REST API または GraphQL を利用!

    システム構成

    Laravel(バックエンド)と Next.js(フロントエンド)は以下のように構成する!

    my-project/
     ├── backend/       ← Laravel(API サーバー)
     ├── frontend/      ← Next.js(フロントエンド)
    

    Laravel のセットアップ

    Laravel プロジェクト作成

    composer create-project --prefer-dist laravel/laravel backend

    Laravel のプロジェクトフォルダが backend/ に作成される!

    環境変数を設定(.env

    .env ファイルの設定

    APP_NAME=Laravel
    APP_ENV=local
    APP_KEY=base64:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
    APP_DEBUG=true
    APP_URL=http://localhost:8000
    
    DB_CONNECTION=mysql
    DB_HOST=127.0.0.1
    DB_PORT=3306
    DB_DATABASE=mydatabase
    DB_USERNAME=root
    DB_PASSWORD=

    環境に合わせて DB_* の設定を変更!

    API の CORS(クロスオリジン)設定

    backend/app/Http/Middleware/VerifyCsrfToken.phpexceptapi/* を追加

    protected $except = [
        'api/*'
    ];

    CORS を許可するために cors.php を設定

    return [
        'paths' => ['api/*'],
        'allowed_methods' => ['*'],
        'allowed_origins' => ['http://localhost:3000'], // Next.js のURL
        'allowed_headers' => ['*'],
        'supports_credentials' => true,
    ];

    フロントエンド(Next.js)からのリクエストを許可する!

    Laravel API のエンドポイント作成

    API ルート(backend/routes/api.php

    use Illuminate\Http\Request;
    use Illuminate\Support\Facades\Route;
    use App\Http\Controllers\UserController;
    
    Route::get('/users', [UserController::class, 'index']);

    UserController.php を作成

    namespace App\Http\Controllers;
    
    use Illuminate\Http\Request;
    use App\Models\User;
    
    class UserController extends Controller {
        public function index() {
            return response()->json(User::all());
        }
    }

    API で GET /api/users にアクセスすると、ユーザー一覧が取得できる!

    Laravel のサーバーを起動

    php artisan serve --host=127.0.0.1 --port=8000

    API サーバーが http://127.0.0.1:8000 で動作する!
    これで Laravel 側の準備は完了!

    Next.js のセットアップ

    Next.js プロジェクト作成

    npx create-next-app@latest frontend

    frontend/ に Next.js のプロジェクトが作成される!

    API の取得を実装(フロントエンド)

    frontend/app/page.tsx に API 通信を追加

    "use client";
    import { useEffect, useState } from "react";
    
    export default function Home() {
      const [users, setUsers] = useState<{ id: number; name: string }[]>([]);
    
      useEffect(() => {
        fetch("http://127.0.0.1:8000/api/users") // Laravel の API
          .then((res) => res.json())
          .then((data) => setUsers(data));
      }, []);
    
      return (
        <div>
          <h1>ユーザー一覧</h1>
          <ul>
            {users.map((user) => (
              <li key={user.id}>{user.name}</li>
            ))}
          </ul>
        </div>
      );
    }

    Next.js から Laravel API (http://127.0.0.1:8000/api/users) にアクセス!
    取得したユーザー一覧を画面に表示!

    Next.js のサーバーを起動

    cd frontend
    npm run dev

    http://localhost:3000 で Next.js が動作!
    フロントエンド + バックエンドの連携完了!

    本番環境用のコマンド

    本番用にビルド

    npm run build

    ここでエラーが出るとエラー内容に応じて修正が必要になります。

    buildができたら本番サーバーを起動します。

    npm run start

    これで本番環境の設定は完了です。

    実際のアプリではWebサーバーの構築をnginxなどで行い、confファイルで各URLの役割を設定することで公開することになります。

  • TypeScriptその③

    useStateの型宣言

    useStateの型宣言は下記にようにします。

    const [count, setCount] = useState<number>(0);

    API のレスポンス型宣言

    APIのレスポンスで受け取った値の型宣言は下記のようにします。

    type User = {
      id: number;
      name: string;
      email: string;
    };
    
    async function fetchUser(): Promise<User> {
      const response = await fetch("https://example.com/api/user/1");
      return response.json(); // `User` 型のデータが返ることを期待
    }

    このようにPromiseオブジェクト(非同期処理の結果を受け取るためのオブジェクト)にくっつける形で定義します。

    ただ、API データ取得関数は色々なデータを受け取る想定で使い回すことが多いので、その場合は型を後から決める仕組み「ジェネリクス」を使用することがあります。

    async function fetchData<T>(url: string): Promise<T> {
      const response = await fetch(url);
      return response.json();
    }
    
    // `User` 型のデータを取得
    fetchData<User>("https://example.com/api/user").then((user) => {
      console.log(user.name);
    });
    
    // `Post` 型のデータを取得
    fetchData<Post>("https://example.com/api/post").then((post) => {
      console.log(post.title);
    });

    このようにレスポンスの型をどんなデータでも扱えるようにすることで、関数の使い回しができます。

    また、fetchData<T>と関数名の後にもジェネリクスをつけていますが、これは「ジェネリクス(T)を使う関数を作る」という意味合いでPromise<T>とセットになっています。

  • TypeScriptその②

    配列の型宣言

    配列の場合の書き方

    const numbers: number[] = [1, 2, 3, 4, 5];
    const numbers: Array<number> = [1, 2, 3, 4, 5];

    どちらも同じ意味となります。

    オブジェクトの型宣言

    オブジェクトの型宣言は下記のように書きます。

    const user: { name: string; age: number } = {
      name: "田中",
      age: 30
    };

    このように、キー毎に型を定義します。

    オブジェクトの場合、「type」を使うと見た目をスッキリすることもできます。

    type User = {
      name: string;
      age: number;
    };
    
    const user: User = {
      name: "田中",
      age: 30
    };

    オブジェクトの配列の型宣言

    オブジェクトが配列になっているデータを扱うことが多いです。

    その場合の書き方は以下です。

    const users: { id: number; name: string }[] = [
      { id: 1, name: "田中" },
      { id: 2, name: "佐藤" }
    ];

    「type」を使えばスッキリかけます。

    type User = {
      id: number;
      name: string;
    };
    
    const users: User[] = [
      { id: 1, name: "田中" },
      { id: 2, name: "佐藤" }
    ];

    このようにオブジェクトの型宣言をする際は「type」を使うと分かりやすくなります。

    propsの型宣言

    propsで受け取る値の型を定義できます。

    type GreetingProps = {
      name: string;
      age: number;
    };
    
    function Greeting({ name, age }: GreetingProps) {
      console.log(`こんにちは、${name} (${age}歳)!`);
    }

    渡ってきたpropsはオブジェクトになっているので、このように書くことでpropsの型宣言ができます。

    関数の型宣言

    関数では引数と戻り値にそれぞれ型宣言できます。

    type User = {
      id: number;
      name: string;
      email: string;
    };
    
    function getUserName(user: User): string {
      return user.name;
    }
  • TypeScriptその①

    本章ではTypeScriptを学習します。実際にNext.jsのプロジェクトはTypeScriptで記述されることが多いので、習得しましょう。

    TypeScriptとは?

    TypeScriptとは、データの型を指定できるJavascriptです。

    Javascriptではデータの型は設定した値によって自動的に決まり、変更するときに異なる型のデータを入れることができますが、TypeScriptは宣言時に型を指定することで、指定した型しか扱えないようにできます。

    このように宣言時に型を指定する言語を「静的型付け言語」、入れる値によって自動的に型が決まる言語を「動的型付け言語」と呼びます。

    TypeScript(静的型付け言語)のメリット

    TypeScriptを使用するメリットは下記です。

    • 間違った型のデータを入れると、実行前にエラーが出る → エラーを事前に発見できる
    • チームで開発するとき、どんなデータが使われるのか一目でわかる → 保守しやすい
    • エディタの補完機能が強化され、ミスが減る → 開発のスピードアップ

    特にチームで開発する際大規模な開発に適しているので、企業で採用されています。

    Next.js + TypeScript

    では実際にNext.jsで使用してみましょう。

    まずはプロジェクトを作成します。

    npx create-next-app@latest my-ts-app

    TypeScriptを使用するか聞かれるので「Yes」を選択してください。

    作成しました。

    このようにファイル名が「tsx」や「ts」になっています。

    基本的な使い方はJavascript版と同じです。

    page.tsxを編集してみます。

    app/page.tsx

    export default function Home() {
      let hello: string = "hello,world!";
      hello = 5;
      return (
        <>
          <h1>{hello}</h1>
        </>
      );
    }

    このように、変数helloに数値を入れようとするとエラーになります。

    このように別の型の値を入れようとした段階でエラーが出るので、アプリが組み上がってきた段階で予期せぬエラーが起こりにくくなるわけです。

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

    Delete(データ削除)

    app/components/DeleteDog.js

    "use client";
    
    export default function DeleteDog({ dogs, setDogs }) {
        const deleteDog = async (id) => {
            const response = await fetch(`http://localhost:3001/dogs/${id}`, {
                method: "DELETE"
            });
    
            if (response.ok) {
                setDogs(dogs.filter((dog) => dog.id !== id));
            }
        };
    
        return (
            <div>
                <h2>🐕 犬を削除</h2>
                {dogs.map((dog) => (
                    <button key={dog.id} onClick={() => deleteDog(dog.id)}>
                        {dog.name}({dog.age}歳)削除
                    </button>
                ))}
            </div>
        );
    }

    コード解説

    DeleteDog コンポーネントを定義

    export default function DeleteDog({ dogs, setDogs }) {

    DeleteDog という React コンポーネントを作成
    dogs(犬のリスト)と setDogs(犬のリストを更新する関数)を props として受け取る

    propsの受け取り方ですが、今回は「分割代入」を使用して受け取っています。

    分割代入ってなに?

    💡 「オブジェクトや配列の中から、特定の値を簡単に取り出せる魔法の書き方」
    💡 「データの中から必要な部分だけをサクッと使う方法」

    propsも複数渡ってくるときはオブジェクトとして扱うので、{ dogs, setDogs }という受け取り方ができます。

    deleteDog 関数を定義

    const deleteDog = async (id) => {

    id(削除したい犬の id)を受け取る関数を作成
    await を使って fetch でデータを削除するため、async をつける

    fetchDELETE リクエストを送る

    const response = await fetch(`http://localhost:3001/dogs/${id}`, {
        method: "DELETE"
    });

    fetch() を使って json-serverDELETE API を呼び出し
    http://localhost:3001/dogs/${id}DELETE リクエストを送る
    id のワンちゃんをデータベース(db.json)から削除する

    ここでresponseが受け取る値はjson-serverデフォルトのものですが、通常はバックエンドアプリ側で設定した戻り値となります。

    if (response.ok) で削除成功かチェック

    if (response.ok) {

    サーバーが「削除成功!」と返したら true になる
    もし削除に失敗したら false になり、リストの更新をしない

    setDogs() で削除したワンちゃんを画面から消す

    setDogs(dogs.filter((dog) => dog.id !== id));

    削除した id 以外のワンちゃんだけ残す削除したidのワンちゃんを削除
    新しい配列をsetDogsでdogsに設定

    ここでは「filter()」メソッドを使用しています。

    filter() とは?

    💡 リストの中から、特定の条件に合うものだけを選び出す魔法のメソッド
    💡 あるものの中から不要なものを捨てて、必要なものだけを残した新しい配列を作成する

    dogs.map((dog) => (...) で犬ごとにボタンを作る

    {dogs.map((dog) => (

    dogs のリストを map() で1匹ずつ取り出す
    ワンちゃんごとに「削除ボタン」を作成

    ボタンにクリックイベント(deleteDog関数)設定

    <button key={dog.id} onClick={() => deleteDog(dog.id)}>

    ボタンを押すと、その犬の iddeleteDog() に渡す
    これで DELETE リクエストが実行され、ワンちゃんが削除される

    起動

    まずjsonサーバーを起動します。

     npx json-server --watch db.json --port 3001

    次に別のターミナルを開いて開発サーバーを起動します。

    npm run dev

    このような画面が表示され、犬の「追加(C)表示(R)更新(U)削除(D)」ができれば成功です。

    まとめ

    今回でAPI通信を使ったCRUDの実装は終わりです。一般的なアプリに必要な基本的な機能を実装しました。

    複雑に見えますが、書き方を覚えて使ってみることで、理解が深まるので是非練習してみてください。

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

    データ更新(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通信します。

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

    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.setDogsstate を更新

    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 リクエストが送られ、サーバーのデータが更新される

    まとめ

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

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

    データ追加(Create)

    app/components/CreatDog.js

    "use client";
    import { useState } from "react";
    
    export default function CreateDog(props) {
        const [newDog, setNewDog] = useState({ name: "", age: 0 });
    
        const addDog = async () => {
            const newEntry = { name: newDog.name, age: newDog.age };
    
            const response = await fetch("http://localhost:3001/dogs", {
                method: "POST",
                headers: { "Content-Type": "application/json" },
                body: JSON.stringify(newEntry)
            });
    
            if (response.ok) {
                const addedDog = await response.json(); // `json-server` が追加したデータを返す
                props.setDogs([...props.dogs, addedDog]);
                setNewDog({ name: "", age: 0 });
            }
        };
    
        return (
            <div>
                <h2>🐕 新しい犬を追加</h2>
                <input
                    type="text"
                    placeholder="名前"
                    value={newDog.name}
                    onChange={(e) => setNewDog({ ...newDog, name: e.target.value })}
                />
                <input
                    type="number"
                    placeholder="年齢"
                    value={newDog.age}
                    onChange={(e) => setNewDog({ ...newDog, age: Number(e.target.value) })}
                />
                <button onClick={addDog}>追加</button>
            </div>
        );
    }

    コード解説

    const [newDog, setNewDog] = useState({ name: "", age: 0 });

    入力フォーム用のstateを設定します。

    addDog(API通信)処理

    「追加」ボタンをクリックしたときのイベントハンドラー「addDog」によるAPI通信の処理を見ていきます。

     const addDog = async () => {
            const newEntry = { name: newDog.name, age: newDog.age };
    
            const response = await fetch("http://localhost:3001/dogs", {
                method: "POST",
                headers: { "Content-Type": "application/json" },
                body: JSON.stringify(newEntry)
            });
    
            if (response.ok) {
                const addedDog = await response.json(); 
                props.setDogs([...props.dogs, addedDog]);
                setNewDog({ name: "", age: 0 });
            }
        };

    1行ずつ説明していきます。

    const addDog = async () => {

    これは関数の定義
    👉 const addDog関数を変数 addDog に代入している
    👉 async () =>「この関数は非同期処理を行う」 という宣言

    💡 「非同期処理って何?」
    サーバーと通信するとき、データが返ってくるまで待つ必要がある
    その間に他の処理を進められるのが「非同期」
    この関数は await を使うために async をつける

    const newEntry = { name: newDog.name, age: newDog.age };

    新しい犬のデータをオブジェクトで作る
    👉 {}オブジェクト(データのまとまり)を作る記号
    👉 name: newDog.name は、newDog.name の値を name というキーに入れる
    👉 age: newDog.age も同じ

    💡 newDog.name って何?」
    これは React の useState で管理されている newDogname の値
    つまり newDog に入力したnameとageを newEntry に入れてる

    const response = await fetch("http://localhost:3001/dogs", {

    サーバー(json-server)にデータを送る
    👉 fetch("URL")指定した URL にデータを送ったり受け取ったりする関数
    👉 await をつけることで、データが返ってくるまで待つ

    💡 await をつけないとどうなる?」
    データを待たずに次の処理が進んで、値が undefined になったりバグる

     method: "POST",

    POST メソッドを使う
    👉 HTTP には 4つの基本的なメソッド(動作)がある

    メソッド何をする?
    GETデータを取得(例:犬の一覧をもらう)
    POSTデータを追加(例:新しい犬を登録する)
    PUTデータを更新(例:犬の年齢を変更する)
    DELETEデータを削除(例:犬をリストから消す)

    💡 POST だから、新しい犬のデータを送る処理になる

    headers: { "Content-Type": "application/json" },

    「このデータは JSON 形式だよ!」とサーバーに伝える
    👉 headers は、サーバーに送るデータの種類を指定する部分
    👉 "Content-Type": "application/json"「送るデータは JSON 形式」と宣言する

    body: JSON.stringify(newEntry)

    データを JSON に変換して送る
    👉 JSON.stringify(データ) は、JavaScript のオブジェクトを JSON 文字列に変換する関数

    if (response.ok) {

    サーバーが「成功!」と返したら実行
    👉 response.ok は、通信が成功 (200 番台のステータス) だったら true になる
    👉 もし false だったら、何かエラーが起きたということ

    💡 response.ok ってどういう意味?
    サーバーが「リクエスト成功!」と返したら true になる
    false の場合はエラー(例:404 Not Found500 Server Error

    const addedDog = await response.json();

    サーバーから返ってきたデータを JSON に変換
    👉 response.json() は、サーバーのレスポンスを JSON 形式のデータに変換する関数
    👉 await をつけることで、データが変換されるまで待つ

    💡 「なぜ response.json() が必要?」
    サーバーから返ってくるデータは JSON 形式の文字列だから、そのままじゃ使えない
    だから response.json() で JavaScript のオブジェクトに変換する

    props.setDogs([...props.dogs, addedDog]);

    React の state を更新して、新しい犬を追加
    👉 props.setDogs() は、親コンポーネントの dogs を更新する関数
    👉 [...props.dogs, addedDog] は、現在の dogs 配列に addedDog を追加する

    💡 なぜ ...props.dogs (スプレッド構文)を使うの?
    React の stateprops.dogs)は、直接変更できない
    だから、新しい配列 [...] を作って、更新する

    setNewDog({ name: "", age: 0 });

    入力フォームをリセット!
    👉 setNewDog({ name: "", age: 0 }) は、新しい犬の情報を 空にする!
    👉 次に入力するときに、前回のデータが残らないようにする!

    💡 setNewDog とは?」
    useState で管理している newDog を更新する関数!
    フォームの中身をリセットするために使う!

    🐵 addDog の処理の流れ

    1️⃣ newEntry を作る(新しい犬の情報)
    2️⃣ fetch でサーバーにデータを送る(POST リクエスト)
    3️⃣ サーバーが処理して response を返す
    4️⃣ response.ok なら、データを JSON に変換
    5️⃣ props.setDogs([...props.dogs, addedDog])state を更新
    6️⃣ setNewDog({ name: "", age: 0 }) でフォームをリセット

    入力フォームで入力された情報をサーバーに送ってデータベースを更新し、通信がOKであれば、ページで表示されている一覧の内容も更新するという流れの処理です

    responseでサーバーから返ってくるデータの内容は実際はバックエンド側のアプリでPOSTに対して何をreturnするかの記述に依存します。今回は、

    👉 json-serverPOST リクエストを送ると、デフォルトで 送られたデータに id を追加して返す仕様に準じています。

    JSX

    return (
            <div>
                <h2>🐕 新しい犬を追加</h2>
                <input
                    type="text"
                    placeholder="名前"
                    value={newDog.name}
                    onChange={(e) => setNewDog({ ...newDog, name: e.target.value })}
                />
                <input
                    type="number"
                    placeholder="年齢"
                    value={newDog.age}
                    onChange={(e) => setNewDog({ ...newDog, age: Number(e.target.value) })}
                />
                <button onClick={addDog}>追加</button>
            </div>
        );

    inputタグの入力フォームでそれぞれnewDogのnameとageをsetNewDog更新用関数を使って更新しています。ここでも、stateの値そのものを変更はできないので、スプレッド構文を使ってそれぞれのキーの値を更新する記述をしています。

    onChange={(e) => setNewDog({ ...newDog, name: e.target.value })}
    onChange={(e) => setNewDog({ ...newDog, age: Number(e.target.value) })}

    「age」を更新するinputタグでは念の為Number()を使って入力された値を数値に変換しています。

    全体体に少し複雑に見えますが、すべてを理解するというよりは「書き方を覚える」というスタンスでOKです。

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

    データ取得(Read)

    app/components/ReadDogs.js

    "use client";
    import { useEffect } from "react";
    
    export default function ReadDogs(props) { // ✅ props から `dogs` を受け取る
        useEffect(() => {
            fetch("/dogs.json")
                .then((res) => res.json())
                .then((data) => props.setDogs(data)) // ✅ `page.js` の `dogs` を直接更新
                .catch((error) => console.error("エラー:", error));
        }, []);
    
        return (
            <div>
                <h1>🐶 犬のリスト</h1>
                <ul>
                    {props.dogs.map((dog) => ( // ✅ `dogs` は `page.js` の `state` を使用
                        <li key={dog.id}>{dog.name}({dog.age}歳)</li>
                    ))}
                </ul>
            </div>
        );
    }
    

    コード解説

      useEffect(() => {
            fetch("http://localhost:3001/dogs")
                .then((res) => res.json())
                .then((data) => props.setDogs(data)) 
                .catch((error) => console.error("エラー:", error));
        }, []);

    useEffectの処理でfetch関数を使って「/dogs.json」からデータを受け取っています。

    fetch("http://localhost:3001/dogs")

    取得先のURLを指定しています。

    .then((res) => res.json())

    受け取ったレスポンス情報をJSONデータに変換しています。

    .then((data) => props.setDogs(data))

    JSONデータをdogsのstateに設定しています。

    .catch((error) => console.error("エラー:", error));

    API通信が何らかの理由でうまくいかなかった際にエラー内容をログ出力する処理を記述しています。

    次にJSXでは、

     {props.dogs.map((dog) => (
      <li key={dog.id}>{dog.name}({dog.age}歳)</li>
      ))}

    「dogs」配列をmapメソッドを使ってリスト表示しています。