【React】郵便番号による予測機能のある住所入力コンポーネントの作成とその課題
フロントエンドエンジニアの茶木です。
郵便番号から住所入力予測ありの住所入力コンポーネントを作成します。 zipcloud という 郵便番号APIと React を組み合わせて作りました。
zipcloudとは
日本郵便のWebサイトで公開されている郵便番号データを再配信するサービスです。
zipcloud
今回使うのはAPIですが、加工済みのCSVの提供、更新の通知も行っています。
APIのラップ
import axios from "axios";
interface ResponseAddress {
address1: string;
address2: string;
address3: string;
zipcode: string;
}
interface Response {
results: ResponseAddress[] | null;
status: number;
}
const URL = "https://zipcloud.ibsnet.co.jp/api/search";
export const getAddress = async (zipcode: string) =>
await axios.get<Response>(URL, { params: { zipcode } });
zipcode
は半角数字7桁 1234567 かハイフン区切りの半角数字 123-4567 の形式のどちらかを受け付けます。
存在する郵便番号であれば、address1
に都道府県、 address2
, address3
に市区町村、zipcode
は郵便番号(ハイフン無し7桁半角数字)が取得できるので、 ResponseAddress
の型に記載しています。
補足: API取得結果の補足
ここでは使用しないため ResponseAddress
に含めていませんが、都道府県コードの prefcode
もAPIの結果にあります。都道府県はコードで管理した方が良いケースの場合は、address1
の代わりに使うと良いでしょう。
それ以外にもヨミガナの kana1
, kana2
, kana3
もAPI取得結果に含まれます。
APIコールの前処理と後処理
import { getAddress } from "../api/zipcode";
const pattern = /^\d{3}-\d{4}$|^\d{7}$/;
function isValidZipcode(input: string): boolean {
return pattern.test(toHalfWidth(input));
}
function toHalfWidth(str: string): string {
return str.replace(/[0-9]/g, (s) =>
String.fromCharCode(s.charCodeAt(0) - 0xfee0)
);
}
interface ResponseAddress {
error: false;
addressBody: string;
zipcode: string;
}
interface ErrorAddress {
error: true;
message: string;
}
export const getAddressFromZipcode = async (
zipcode: string
): Promise<ResponseAddress | ErrorAddress> => {
const fixed = toHalfWidth(zipcode);
if (!isValidZipcode(fixed)) {
return { error: true, message: "7桁の郵便番号を入力してください" };
}
const res = await getAddress(fixed);
if (!res.data.results) {
return { error: true, message: "存在しない郵便番号のようです" };
}
return {
error: false,
addressBody: `${res.data.results[0].address1} ${res.data.results[0].address2} ${res.data.results[0].address3}`,
zipcode: res.data.results[0].zipcode,
};
};
getAddressFromZipcode
の中で前処理・後処理を行います。
前処理
APIコール前に、郵便番号形式を満たさないものを弾いて無駄なコールをせず、すぐにエラーメッセージを返すようにします。また、全角数字から半角数字への変換も行います。
後処理
郵便番号の形式は正しいが、存在しない郵便番号があります。これらはAPIコール後にエラーとして判明するので、コール後にエラーメッセージを返すようにします。
正常に取得が完了した場合 address1
, address2
, address3
を繋げて住所 addressBody
を作成します。実は取得結果の results
は配列です。これは郵便番号が複数の市区町村を指すケースがあるためです。ユーザーが期待する市区町村は判断がつかないため、ここでは一律で配列の先頭を使用します。
住所入力コンポーネントとして組み立てる
export default function Index(): ReactElement | null {
const [zipcode, setZipcode] = useState("");
const [addressBody, setAddressBody] = useState("");
const [suffix, setSuffix] = useState("");
const [zipcodeErrorMessage, setZipcodeErrorMessage] = useState("");
useEffect(() => {
getAddressFromZipcode(zipcode).then((res) => {
if (!zipcode) return setZipcodeErrorMessage("");
if (res.error) return setZipcodeErrorMessage(res.message);
setZipcode(res.zipcode);
setAddressBody(res.addressBody);
setZipcodeErrorMessage("");
});
}, [zipcode]);
return (
<PageBase title={"AddressFromZipcode"}>
<div>
<h1 className={styles.title}>Address</h1>
<TextField
label="郵便番号"
value={zipcode}
onChange={(e) => setZipcode(e.target.value)}
error={Boolean(zipcodeErrorMessage)}
helperText={zipcodeErrorMessage}
/>
<TextField
label="都道府県・市区町村"
value={addressBody}
onChange={(e) => setAddressBody(e.target.value)}
/>
<TextField
label="番地・建物名・部屋番号"
value={suffix}
onChange={(e) => setSuffix(e.target.value)}
/>
<Button
variant="contained"
color="primary"
disabled={Boolean(
!zipcode || !addressBody || !suffix || zipcodeErrorMessage
)}
onChange={submit}
>
登録
</Button>
</div>
</PageBase>
);
}
addressBody
は郵便番号入力からも取得できる範囲の、都道府県および市区町村です。手入力も可能です。suffix
は郵便番号で特定されない、番地や建物名や部屋番号などの部分です。
zipcode
は郵便番号で、郵便番号形式として正しいときAPIコールをし 結果が正常であれば addressBody
を書き換えます。エラーは zipcodeErrorMessage
に出力されます。これらは、 useEffect
を使って zipcode
の変化を見張り実行しています。
登録ボタンの disabled
には、未入力とエラーメッセージのバリデーションチェックをしています。
課題
いくつかの課題があります。
- 住所の候補が複数あるケースがあり、先頭以外を握り潰している
- 先に住所を入力してから、郵便番号を入力すると住所が予測で上書きされる
住所の候補が複数あるケースがあり、先頭以外を握り潰している
まず、完全に悪い対応とは言えなさそうです。ユーザーの住所の直接入力ができるので予測が間違っていても手動修正できるという考え方もあります。
ふまえて、複数候補からユーザーが選択するのもいいかもしれないです。たとえば、セレクトのポップアップが表示されユーザーが選択するような形式が考えられます。
先に住所を入力してから、郵便番号を入力すると住所が予測で上書きされ消える
これも気になる点です。
代替案はいくつかありそうです
- 手動で住所が入力された場合は、郵便番号の予測をしない
- 郵便番号を完了しないと、住所入力が enable にならない
- 住所の予測入力ボタンを配置して、ユーザーが明示的に押したときに上書きする
- 郵便番号を入れると予測のポップアップが表示されユーザーが選択する
1. は実装上の難しさがあります。ユーザー入力なのか、予測の住所が表示されているのか、予測の住所をユーザーが編集したのか判断しないといけないためです。
2. は郵便番号の入力が必須になってしまう点と、ユーザーに入力順を強制する点が良くないです。
3. はときどき見かける実装です。ボタンを押す手間がありますが、ユーザーの入力を確認しているので堅牢だと言えそうです。
4. はユーザーの判断で予測住所を決定できる点と、前項の複数候補の対応も同時に解消できそうなので、これがベストな気配がします。
おわりに
次回以降で、課題をふまえて複数予測を解決した郵便番号のコンポーネント作成に挑戦してみようと思います。
Gaji-Labo は Next.js, React, TypeScript 開発の実績と知見があります
フロントエンド開発の専門家である私たちが御社の開発チームに入ることで、バックエンドも含めた全体の開発効率が上がります。
「既存のサイトを Next.js に移行したい」
「人手が足りず信頼できるエンジニアを探している」
「自分たちで手を付けてみたがいまいち上手くいかない」
フロントエンド開発に関わるお困りごとがあれば、まずは一度お気軽に Gaji-Labo にご相談ください。
オンラインでのヒアリングとフルリモートでのプロセス支援にも対応しています。
Next.js, React, TypeScript の相談をする!