Reactで金融機関検索APIをたたいてみた
はじめに
今回は、JavaScript・Reactの学習として、非同期で金融機関検索APIをたたいて、useState・useEffectなどを使用して、銀行名入力フォームを実装してみました。
記事の内容としては、コードの内容を解説するものとなっていますので、もし間違っている内容があればご指摘いただけると幸いです。
(UI部分のみの実装になります。)
概要
金融機関検索APIの「銀行くん」を使用して、銀行振込フォームを実装します。
今回の実装内容は以下になります。
- 銀行名を入力するフォームで、1文字ずつapiを叩いて、一致する銀行名の候補をリスト形式で表示します。
- サジェストから銀行名を選択することができる。
- 支店名を入力すると、支店コードが自動で入力される。
使用技術
- React (18.2.0)
- 「銀行くん」金融機関検索API (0.1.6)
デモ
codesandboxを使用して、デモを実装してみました。
githubはこちら
コード
import { useState, useEffect } from "react";
import "./styles.css";
export default function App() {
const [bankName, setBankName] = useState("");
const [suggestedBanks, setSuggestedBanks] = useState([]);
const [isFocused, setIsFocused] = useState(false);
const [isSuggestedOpen, setIsSuggestedOpen] = useState(true);
const [bankCode, setBankCode] = useState("");
const [branchCode, setBranchCode] = useState("");
const [branchName, setBranchName] = useState("");
const [allBanks, setAllBanks] = useState([]);
// 銀行情報を取得
useEffect(() => {
const fetchAllBanks = async () => {
let fetchedBanks = [];
let currentPage = 1;
let hasMore = true;
while (currentPage <= 24) {
const api = `https://bank.teraren.com/banks.json?page=${currentPage}`;
const response = await fetch(api);
const data = await response.json();
if (data && data.length > 0) {
fetchedBanks = fetchedBanks.concat(data);
currentPage += 1;
} else {
break;
}
}
setAllBanks(fetchedBanks);
};
fetchAllBanks();
}, []);
// 銀行名検索で、入力値に一致するデータをサジェストで表示させる
const handleBankNameInputChange = async (e) => {
setIsSuggestedOpen(true);
const value = e.target.value;
setBankName(value);
if (value) {
const filteredData = allBanks.filter(
(bank) =>
bank.name.includes(value) ||
bank.hira.includes(value) ||
bank.kana.includes(value),
);
setSuggestedBanks(filteredData);
} else {
setSuggestedBanks([]);
}
};
// focusを判定
const handleFocus = () => {
setIsFocused(true);
};
const handleBlur = () => {
setIsFocused(false);
};
const bankNameMessage = () => {
if (!isFocused && bankName.length === 0) return null;
if (bankName.length === 0 || suggestedBanks.length === 0) {
return <li className="error-text">該当の銀行は見つかりません</li>;
}
return suggestedBanks.map((bank) => (
<li
key={bank.code}
className="bank-name"
onClick={() => handleBankNameClick(bank.normalize.name, bank.code)}
>
{bank.normalize.name}
</li>
));
};
// サジェストされた銀行名を選択
const handleBankNameClick = (name, code) => {
setBankName(name);
setBankCode(code);
setSuggestedBanks([]);
setIsSuggestedOpen(false);
};
// 選択した金融機関コードを基に全支店情報を取得
const fetchAllBranches = async (bankCode) => {
let allBranches = [];
let currentPage = 1;
let hasMore = true;
while (hasMore) {
const api = `https://bank.teraren.com/banks/${bankCode}/branches.json?page=${currentPage}`;
const response = await fetch(api);
const data = await response.json();
if (data && data.length > 0) {
allBranches = allBranches.concat(data);
currentPage++;
} else {
hasMore = false;
}
}
return allBranches;
};
// 支店名入力
const handleBranchNameChange = async (e) => {
const value = e.target.value;
setBranchName(value);
if (bankCode.length === 4) {
const branches = await fetchAllBranches(bankCode);
const branch = branches.find((branch) => branch.name.includes(value));
if (branch) {
setBranchCode(branch.code);
} else {
setBranchCode("");
}
}
};
// 支店コード入力
const handleBranchCodeChange = async (e) => {
const value = e.target.value;
setBranchCode(value);
if (bankCode.length === 4 && value.length === 3) {
fetch(`https://bank.teraren.com/banks/${bankCode}/branches/${value}.json`)
.then((response) => response.json())
.then((json) => {
setBranchName(json.name);
})
.catch((error) => {
setBranchName("");
});
}
};
return (
<>
<form className="form">
<div>
<label htmlFor="searchBank">銀行名</label>
<input
id="searchBank"
className="input-form"
placeholder="銀行名を検索する"
value={bankName}
onChange={handleBankNameInputChange}
onFocus={handleFocus}
onBlur={handleBlur}
></input>
{isSuggestedOpen ? (
<ul className="bank-name-suggested">{bankNameMessage()}</ul>
) : (
""
)}
</div>
<div className="form-box-code">
<label htmlFor="BankCode">金融機関コード</label>
<input
id="BankCode"
className="input-form"
placeholder="例)0001"
value={bankCode}
></input>
</div>
<div className="form-box-branch">
<label htmlFor="inputBranchName">支店名</label>
<input
id="inputBranchName"
className="input-form"
placeholder="例)丸の内中央"
type="text"
value={branchName}
onChange={handleBranchNameChange}
></input>
</div>
<div className="form-box">
<label htmlFor="inputBranchCode">支店コード</label>
<input
id="inputBranchCode"
className="input-form"
placeholder="例)000"
type="text"
value={branchCode}
onChange={handleBranchCodeChange}
></input>
</div>
</form>
</>
);
}
コード解説
useStateとuseEffectとは
まず、今回の実装内で使用したuseState
とuseEffect
について簡単にまとめてみます。
useState
useState
はコンポーネントの状態を管理するためのフックになります。
今回のコードの、const [bankName, setBankName] = useState("");
を例にすると、、、
-
bankName
→ 現在の状態を表し、id="searchBank"
のinput要素のvalueに入る値を想定しています。 -
setBankName
→bankName
の状態を更新するために使用されます。handleBankNameClick
内でsetBankName(name);
とすることで、name
(サジェストで選択した銀行名)をbankName
に渡して、状態を更新します。そうすることで、id="searchBank"
のinput要素のvalueに、サジェストから選択した銀行名が挿入されます。 -
useState("");
→()
には初期値が入ります。今回は初期値にはなにも設定していません。
useEffect
useEffect
は、コンポーネントがレンダリングされた後に、コンポーネントのレンダリングとは別に処理を実行するために使用します。今回のコードでは、コンポーネントが初めて描写された(マウントされた)タイミングでfetchAllBanks
が実行されて、APIからデータを取得し、銀行情報を全て取得しています。
また、依存配列([]
)が空のため、コンポーネントが初めて描写された時に一度だけ実行されて、全ての銀行情報を保存しています。
マウントとは
コンポーネントが初めてUIに描写されるプロセスのことをさします。
依存配列とは
この配列は、useEffect
などのフックで指定することができ、フックがコンポーネントのレンダリングプロセスとは独立して実行されるタイミングを設定することができます。
-
[]
空の依存配列- コンポーネントがマウントされた後に一度だけ実行されます。これは、APIからのデータを取得を最初に一度だけ行いたい時などに使用されます。
- 変数を含む依存配列
- 設定した変数が変更されたタイミングで実行されます。これは、propsの変更などに応じて処理を実行したい場合などに使用されます。
では、その他の実装についても解説してみたいと思います。
銀行名・金融機関コード入力フィールドについて
以下の箇所になります。
<div>
<label htmlFor="searchBank">銀行名</label>
<input
id="searchBank"
className="input-form"
placeholder="銀行名を検索する"
value={bankName}
onChange={handleBankNameInputChange}
onFocus={handleFocus}
onBlur={handleBlur}
></input>
{isSuggestedOpen ? (
<ul className="bank-name-suggested">{bankNameMessage()}</ul>
) : (
""
)}
</div>
<div className="form-box-code">
<label htmlFor="BankCode">金融機関コード</label>
<input
id="BankCode"
className="input-form"
placeholder="例)0001"
value={bankCode}
></input>
</div>
この入力フィールドでは、ユーザーが入力したテキストをもとにして、動的にサジェストとして銀行名を表示し、サジェストから銀行名を選択することができるようになっています。
また、銀行名を選択すると、該当の金融機関コードも自動的に入力されます。
【使用されている状態管理】
const [bankName, setBankName] = useState(""); // 現在、入力されている銀行名
const [suggestedBanks, setSuggestedBanks] = useState([]); // ユーザーの入力に基づいてフィルだリングされた銀行名のリスト
const [isFocused, setIsFocused] = useState(false); // 入力フィールドがフォーカスされているかどうか
const [isSuggestedOpen, setIsSuggestedOpen] = useState(true); // サジェストにリストが表示されるかどうか
const [bankCode, setBankCode] = useState("");
const [allBanks, setAllBanks] = useState([]); // APIから取得した全ての銀行情報
- ユーザーが入力フィールドに銀行名を入力すると、
handleBankNameInputChange
関数が呼び出されて、フィリタリングされた銀行名がサジェストリストに表示されます。
【handleBankNameInputChange
関数で行われていること】-
setIsSuggestedOpen(true);
isSuggestedOpen
がtrue
になるため、ユーザーが入力し始めたタイミングで、サジェストリストが表示されます。 -
const value = e.target.value;
とsetBankName(value);
ユーザーの入力値を取得し、bankNameの状態を更新します。これは、id="searchBank"
の入力フィールドのvalue
と同期しています。 -
if (value) ~
ユーザーが入力をしている場合に、以下の処理が実行されます。- APIから取得した全ての銀行情報
allBanks
に対して、フィルタリングを行います。このフィルタリングは、APIのname
(銀行名),hira
(ぎんこうめい),kana
(ギンコウメイ)がユーザーの入力値に含まれているかをフィルタリングしています。 - フィルタリングにより取得した銀行名は、
setSuggestedBanks(filteredData);
によりサジェストリストに表示されます。ユーザーの入力値が空の場合は、setSuggestedBanks([]);
を返します。
- APIから取得した全ての銀行情報
-
-
handleFocus
とhandleBlur
で、サジェストリストの表示を管理しています。 -
bankNameMessage
関数は、ユーザーの入力に合わせて、動的にサジェストリストを生成しています。-
if (!isFocused && bankName.length === 0) return null;
は、先ほどのhandleFocus
とhandleBlur
で状態を管理していたisFocused
がfalseで、入力フィールドがフォーカスされていなくて、入力値が空の場合はサジェストを表示させないようにしています。 -
if (bankName.length === 0 || suggestedBanks.length === 0) ~
こちらの条件分岐処理の中では、入力値が空の場合・サジェストに銀行名がない場合は、エラーメッセージを表示して、そうでない場合はmap関数
を使用して、銀行名のサジェストリストを表示しています。
-
-
handleBankNameClick
関数は、サジェストリストから選択された銀行名と金融機関コードをそれぞれの入力フィールドに挿入する処理を行なっています。
具体的には、setBankName(name);
でbankName
の状態を更新し、setBankCode(code);
でbankCode
の状態を更新しています。そして、setSuggestedBanks([]);
でサジェストリストを空にして、setIsSuggestedOpen(false);
でサジェストを非表示にしています。
支店名・支店コード入力フィールド
以下の箇所になります。
<div className="form-box-branch">
<label htmlFor="inputBranchName">支店名</label>
<input
id="inputBranchName"
className="input-form"
placeholder="例)丸の内中央"
type="text"
value={branchName}
onChange={handleBranchNameChange}
></input>
</div>
<div className="form-box">
<label htmlFor="inputBranchCode">支店コード</label>
<input
id="inputBranchCode"
className="input-form"
placeholder="例)000"
type="text"
value={branchCode}
onChange={handleBranchCodeChange}
></input>
</div>
こちらは、先ほど選択した銀行に存在する「支店名」と「支店コード」を入力する実装になります。
【使用されている状態管理】
const [branchCode, setBranchCode] = useState("");
const [branchName, setBranchName] = useState("");
-
fetchAllBranches
関数では、先ほど選択した銀行の金融機関コードに該当する全支店情報を、非同期で取得しています。-
async
とawait
について(今回の実装を基に解説)-
async
→fetchAllBranches
関数を非同期関数として定義することができます。非同期関数とすることで、内部での非同期処理の完了を待ってから、Promise
を返すことができます。こちらは、bankCode
に基づきAPI通信が行われています。 -
await
→await
で定義したAPIのリクエストが完了し、レスポンスが返ってくるまで処理を待機します。
ようは、fetchAllBranches
関数が呼び出される → 最初に定義されている変数初期化 → while文の処理 →await
でAPIのレスポンスが返ってくるまで待機 → APIリクスト完了 →response.json()
でレスポンスをjsonで解析 → jsonにdataがあれば、allBranches
配列に追加 →while
ループ終了 →return allBranches;
という流れになり、https://bank.teraren.com/banks/${bankCode}/branches.json?page=${currentPage}
というAPIの実行が完了するまで、他の処理をブロックせずにfetchAllBranches
関数内の処理は待機して、APIのレスポンスが返ってきたら次の処理に進めるということになります。
-
-
-
handleBranchNameChange
関数は、ユーザーが入力した支店名に基づいて、支店コードを設定する処理を行なっています。- ユーザーの入力値を
setBranchName(value);
によって、barnchName
の状態を更新して、入力フォームに値を挿入しています。 - 次に、
const branches = await fetchAllBranches(bankCode);
で、fetchAllBranches
関数を呼び出して、指定された金融機関コードbankCode
に基づいた全支店情報を取得します。ここで、await
を使用している理由としては、fetchAllBarnches
関数が非同期処理を行なっているため、その完了を待つ必要があるからになります。 - 次に、
branches.find((branch) => branch.name.includes(value));
でユーザーが入力した支店名と一致する支店名を先ほど取得したリストから検索し、もし一致した支店名があれば、setBranchCode(branch.code);
でbranchCode
の状態に設定します。
- ユーザーの入力値を
以上の流れで、支店名入力フィールドに入力された値が存在している支店だった場合、支店コード入力フィールドにも自動的に値が挿入されます。
次に、支店コード入力フィールドです。
-
handleBranchCodeChange
関数は、ユーザーが入力した支店コードを基に、支店名を設定する処理を行なっています。- こちらも今までと同様に、
setBranchCode(value);
で入力された値で、branchCode
の状態を更新します。 - 次に、有効な金融機関コードが入力されている場合、
fetch関数
で金融機関コードと支店コードに対応するAPIエンドポイントからデータを取得します。そして、.then
メソッドを使用して、取得したレスポンスをjson形式で解析し、支店名を取得し、setBranchName(json.name);
で、branchName
の状態に設定します。また、.catch
でエラーハンドリングを行い、エラー時にはbranchName
に空の値が入るようにしています。
- こちらも今までと同様に、
最後に
今回は、フロント側のみの実装でしたが、今後はバックエンド側のアウトプットも行なっていきたいと思います。
Discussion
https://bank.teraren.com/ の作者です。
1回で全銀行のリストを取得できるパラメータがあります!