【Day17】インスタ用のキャプションを生成する（中編）

## はじめに

勉強とアウトプットを兼ねて、Webアプリ開発の記録を記事にしてみます。

個人のメモ的な側面が強いです。
Web開発初心者すぎて至らぬところだらけだと思いますが、暖かい目で見ていただければ幸いです。

↓ これまでの内容 ↓
[【Day0】アプリが完成するまで毎日投稿](https://qiita.com/midorigame/items/f9b9a445c945b4a837b1)
[【Day1】開発環境を考える](https://qiita.com/midorigame/items/e26344599e2ffaa22033)
[【Day2】開発環境を構築する、、、はずでした](https://qiita.com/midorigame/items/3f57f2691a8e1074adb8)
[【Day3】まずはフロントだけ開発環境を構築する](https://qiita.com/midorigame/items/9de27ba72aaef504a833)
[【Day4】バックエンドの開発環境を整えつつ、マルチコンテナ対応のDevcontainerにする](https://qiita.com/midorigame/items/5b95374cd85f715a6498)
[【Day5】お試し実装！ファイルをアップロードしてバックエンドで処理する](https://qiita.com/midorigame/items/9663f0063b95b501c08a)
[【Day6】Docker Desktopが起動しなくなり泣きながら原因調査する](https://qiita.com/midorigame/items/d07df1caf05267d0609d)
[【Day7】画像にフレームとExif情報を付けてみる](https://qiita.com/midorigame/items/e2d85ce8dd7a5bcb5daf)
[【Day8】フロントに表示する画像のサイズを小さくする](https://qiita.com/midorigame/items/f7f5f9b14fb79259c716)
[【Day9】フロントエンドとバックエンドをそれぞれデプロイする](https://qiita.com/midorigame/items/4c8037024e667ff1105b)
[【Day10】結局コンテナ1つで開発する方が楽かもしれない](https://qiita.com/midorigame/items/60b484b2007c3aab474b)
[【Day11】アプリが一通り完成！！](https://qiita.com/midorigame/items/38ddccc09f0625aaebd6)
[【Day12】FastAPIのコードをリファクタリングする](https://qiita.com/midorigame/items/72ed91ff9add5504fab9)
[【Day13】機能実装！フレームに記載するExif情報を編集できるようにする（前編）](https://qiita.com/midorigame/items/790b0fcd54620f9fb573)
[【Day14】ReactのリファクタリングとUI変更を同時にやった](https://qiita.com/midorigame/items/b492ae717e59fb137b70)
[【Day15】機能実装！フレームに記載するExif情報を編集できるようにする（後編）](https://qiita.com/midorigame/items/9a649aa2a0a17d1db552)
[【Day16】インスタ用のキャプションを生成する（前編）](https://qiita.com/midorigame/items/4aa9bf4f1d05180436db)

## 前回やったこと

アップロードした写真から自動でキャプションを生成する機能の第一歩として、まずはフロント側でキャプション表示エリアを作成して、タップ/クリックで簡単にコピーできるようにしました。

## 今回やったこと

バックエンドのFastAPIにテキストをレスポンスとして返却するAPIを作成しました。
このテキストがキャプションになるのですが、現段階ではまだ定数にしています。

### API設計

現在作成中のWebアプリでは画像をフレーム化するAPIをすでに作っていて、このAPIでテキストも一緒に返却するようにするか迷いました。

しかし、１つのAPIに機能を詰め込みすぎると後々の仕様変更で大変になると思い、画像をフレーム化するAPIとキャプションのテキストを返却するAPIを分けることにしました。

APIは分けましたが、呼び出すときは同期的に呼び出して、両方の処理が終わってから次に進めるようにしました。
こちらは`await Promise.all()`で実現しています。

```react:UploadButton.tsx
（中略）
    try {
      const resizedBlob = await resizeImageWithExif(selectedFile);

      const formData = new FormData();
      formData.append("file", resizedBlob, selectedFile.name);
      formData.append("show_exif", String(showExif));
      formData.append("exif", JSON.stringify(exifData));

      // 並列リクエスト（Promise.all）
      const [imageRes, captionRes] = await Promise.all([
        axios.post(import.meta.env.VITE_UPLOAD_IMAGE_API_URL, formData, {
          responseType: "blob",
        }),
        axios.post(import.meta.env.VITE_CAPTION_API_URL, formData),
      ]);

      const imageUrl = URL.createObjectURL(imageRes.data);
      setProcessedImage(imageUrl);

      const captionText = captionRes.data.caption || "";
      console.log("captionRes.data:", captionRes.data);
      setCaption(captionText); // キャプション用 state を事前に用意しておく
      console.log(`captionText : ${captionText}`);

      setStatusMessage("画像とキャプションを取得しました");
    } catch (error) {
      console.error("アップロードに失敗:", error);
      setStatusMessage("アップロードに失敗しました");
    }
（中略）
```

##　ハマったこと

APIで受け取ったテキストがキャプション表示エリアに表示されないな～～～、と悩んでいたのですが、ReactのPropsの仕様を理解していなかったことが原因でした。

今回は分割代入でPropsを渡していたのですが、代入時の変数と引数の名称が一致しておらず、値を取得できていませんでした。

```text:Chat GPTの回答
// 1. 分割代入（名前が一致してる必要あり）
const Component: React.FC<{ caption: string }> = ({ caption }) => {
  return <div>{caption}</div>;
};

// 2. props からアクセス（この場合は自由に名前つけられる）
const Component = (props: { caption: string }) => {
  return <div>{props.caption}</div>;
};
```

Propsの受け渡しには注意しないとですね。

## おわりに

今日は作業時間が取れず、軽めの進捗になりました。
次回はアップロードされた画像からキャプションを生成するロジックを作成して、キャプション自動生成機能を完成させます！！