APIからデータを取得してブラウザに表示する方法を調べました【React/axios/useEffect/zipcloud】

JavaScript

ReactでAPIデータを取得して何かをする。という場面が多いので、今後の雛形としてメモしておきます。

住所検索アプリ(サンプル)

サンプルとして、郵便番号から住所を検索できるアプリを作りました。
ユーザーが入力した郵便番号をAPIに送信して、返却された住所をブラウザに表示します。

入力フォームに郵便番号を入力して「住所検索」ボタンをクリックすると、住所が表示されます。
実際の動作は以下からご確認ください!
※コンソールログも合わせてご確認ください。

APIは、株式会社アイビスが運営する配信サービス「zipcloud」を使用させてもらいました。
郵便番号を含むリクエストURLを送ると、住所を含んだデータを返却してくれます。

公式のサイトはこちらです。

郵便番号検索API - zipcloud
日本郵便が公開している郵便番号データを検索する機能をRESTで提供しています。

コード(全体)

全体のコードはこちらになります。
実際はコンポーネントで分割しますが、サンプルなので1ファイルにまとめています。

App.js

import { useState, useEffect } from "react";
import axios from "axios";
import "./styles.css";

export default function App() {
  const [zip, setZip] = useState(""); // 郵便番号(入力)
  const [query, setQuery] = useState("");
  const [resultTxt, setResultTxt] = useState(""); // 住所

  useEffect(() => {
    const fetchData = () => {
      console.log("データを取得します");
      console.log(query);

      axios
        .get(`https://zipcloud.ibsnet.co.jp/api/search?zipcode=${query}`)
        .then((res) => {
          console.log(res);
          // APIがうまく動作していない時のエラー
          if (res.status !== 200) {
            throw new Error("APIがうまく動作していないようです");
          } else {
            // 郵便番号の桁数が不正の場合のメッセージ
            if (res.data.message) {
              setResultTxt(res.data.message);
              return;
            }

            // 郵便番号が存在しない場合のエラーメッセージ
            if (res.data.results == null) {
              setResultTxt("郵便番号が見つかりませんでした");
              return;
            }

            // 取得した住所を格納
            let getAddress = res.data.results[0];

            setResultTxt(
              `〒${getAddress.zipcode}\n${getAddress.address1}${getAddress.address2}${getAddress.address3}`
            );
          }
        })
        .catch((err) =>
          setResultTxt(`データがうまく取得できませんでした。${err}`)
        );
    };

    if (query) fetchData(); // 郵便番号が入力されてたら実行
  }, [query]); /// zipの値が更新されたら実行

  // 住所検索をクリックした時
  const onClickGetArea = () => {
    console.log("住所検索をクリックしました");

    // 未入力だったらアラートを表示
    if (zip === "") {
      alert("郵便番号を入力してください");
      return;
    }

    // データ取得
    setQuery(zip);
  };

  const inputStyle = {
    border: "1px solid #ccc",
    padding: "5px 10px",
    borderRadius: "4px",
    marginRight: "10px"
  };

  const h1Style = {
    fontSize: "1.2em",
    color: "#b09851",
    background: "#e9e1c8",
    padding: "5px 10px"
  };

  return (
    <div>
      <h1 style={h1Style}>住所検索サンプル</h1>
      <p>
        郵便番号を入力して「住所検索」ボタンをクリックしてください
        <br />
        <span style={{ fontSize: ".8em" }}>例:1050011</span>
      </p>
      <input
        style={inputStyle}
        type="text"
        value={zip}
        placeholder="郵便番号を入力してください"
        onChange={(e) => setZip(e.target.value)}
      />
      <button onClick={onClickGetArea}>住所検索</button>
      <p>{resultTxt}</p>
    </div>
  );
}

ポイントとなる点

忘れそうなので、ポイントとなるところをメモしておきます!
ハマったポイントは2つだけです!

  • useEffectでAPIにアクセスするタイミングを制御する
  • useStateで入力した値を都度更新する

APIデータを取得する(axios)

APIデータを取得するのにaxios(アクシオス)を導入します。
fetchでも良いのですが、データを非同期で取得してくれるし、コードがシンプルにかけるのでこちらを使用します。

次のコマンドでインストールしておきます。

$ npm install axios --save

インストールしたら、axiosを使うファイルにimportしておきます。

import axios from "axios";

axiosの構文

構文は次の通りです。このように直感的に記述できます!
then()をつなげて、コードの実行順を制御することもできます。

axios
  .get(<APIのURL>)
  .then((res) => {
    // データが取得できた時の処理
    console.log(res);  // resに取得データがオブジェクトで格納される
    // 例外エラーを発生させる
    throw new Error("<表示させるエラー>");
  })
  // データが取得できなかった時の処理
  .catch((err) => alert("データがうまく取得できませんでした"))
  // 最終的に実行される処理
  .finally(() => {console.log('取得完了!')});

zipcloud」から郵便番号を取得するには、次のURLをAPIに送信する必要があります。

https://zipcloud.ibsnet.co.jp/api/search?zipcode=<郵便番号>

APIから住所を取得するには、フォームに入力された郵便番号を、上記のURLに当てはめる必要があります。郵便番号はuseStateで管理します。

フォームに入力された値を更新する(useState)

フォームに入力した値はuseStateで管理して、入力したら都度更新されるようにします。
以下は入力した値を表示する簡単なサンプルです。

import { useState } from "react";
import "./styles.css";

export default function App() {
  const [zip, setZip] = useState(""); // 郵便番号(入力)

  return (
    <div>
      <p>郵便番号を入力してください</p>
      <input onChange={(e) => setZip(e.target.value)} />
      <p>入力結果:{zip}</p>
    </div>
  );
}

onChangeイベントで、文字が入力されるたびにフォームの値をsetZip()の引数にセットします。
※初期値は空の文字列にしておきます。

そうすることで、変数zipに入力結果(ここでは郵便番号)が格納されます。
実際の動きはこちらで確認できます!

これで入力された値(郵便番号)を取得することができました!

「住所検索」をクリックしたらAPIからデータを取得する(useEffect)

「住所検索」をクリックした時に、APIにデータを送信します。
コンソールでログを確認したところ、なんとそれ以外でも通信が発生していました。
読み込んだ時と、文字を入力した時に発生しています。

読み込んだ時はなんとなく分かるのですが、文字を入力しても通信が発生しています。
これは副作用と呼ばれる現象で、関連するコンポーネントも連鎖して再レンダリングされる仕組みから発生します。

副作用とは?
StateやPropsの更新、DOMの変更など、要素に何かしら変更があった際、ReactはDOMを再レンダリングします。
その際、関連するコンポーネントも連鎖して再レンダリングされます。

今回、入力した値はuseState(変数zip)で管理しており、APIに送信するURLに組み込んでいます。入力するごとにStateは更新され、関連するURLも更新されデータ通信が発生しています。

useEffectで副作用を制御する

useEffectを使って、「住所検索」をクリックした時だけAPIにアクセスするように制御します。
構文は以下の通りです。

// DOMをレンダリングした後に実行
useEffect(() => {
  // 処理する内容
}, []); // 依存する変数を配列で指定。空の場合、一度だけ実行

ポイントは、第二引数に、依存する変数を設定することです。
こうすることで、設定した変数に更新があった時、useEffectに記述した処理が実行されます。
なお、第二引数の値の更新に関係なく、最初の1回だけは必ず処理が実行されます。

以下は、入力した時だけイベントを発生させる簡単なサンプルです。

先程の例と異なり、「住所検索」をクリックした時だけ、通信が発生することを確認できました。

useEffectが依存している変数は、入力した郵便番号(zip)ではなく、queryです。この値もuseStateで管理しており、「住所検索」をクリックするごとに更新されます。

queryzipをセットすることで、「住所検索」をクリックした時点の郵便番号が引き渡されることになります。
リアルタイムで住所を表示させる場合、依存変数をzipにするのもありかもしれません。。

これで「住所検索」をクリックしたら、APIに住所を送信する仕組みが完成しました!

なお、useEffectは読み込み時に実行されるので、queryに値があるだけfetchData()を実行するようにしました。

APIから取得した住所をブラウザに表示する(useState)

APIから取得した値をブラウザに表示します。
以下は「住所検索」をクリックしたら、住所を表示する簡単なサンプルです。

通信は発生せず、以下のサンプルデータを表示しているだけです。

// APIで取得されるデータを想定
const res = {
  data: {
    message: null,
    results: [
      {
        address1: "東京都",
        address2: "港区",
        address3: "芝公園",
        kana1: "トウキョウト",
        kana2: "ミナトク",
        kana3: "シバコウエン",
        prefcode: "13",
        zipcode: "1050011"
      }
    ],
    status: 200
  }
};

特にポイントとなるところはありませんが、改めてログを確認すると、「住所検索」をクリックした後にuseEffectの処理が実行されているのが分かります。

まとめ

useEffectは副作用を制御できますが、意味を理解するのに時間がかかりました。。
APIからデータを取得する際、必ず成功するわけではないので、エラー処理を実装するのも難しかったです。

これで雛形ができたので、以降はコピペでいけるはず、、、。変なところがあったら都度修正します!

コメント