😎
郵便番号から住所自動入力フォームをReact + TypeScriptで簡単実装(jposta使用)
郵便番号を打ち込むと住所が自動入力されるフォームを作る機会があり、jpostaを使って簡単に作れたので、備忘として残しておきます。
jpostaとは?
郵便番号をgetAddress()に、await getAddress('1000001')のように渡すと、
{ pref: "東京都", prefNum: 13, city: "千代田区", area: "千代田" }のように、
都道府県、都道府県コード、市区町村、町域の4つがオブジェクトとして返ってくるライブラリです。
(郵便番号はハイフンありでawait getAddress('100-0001')のように渡してもOKです。)
2024年6月にリリースされた比較的新しいライブラリということもあるのか、
ほぼ一ヶ月に一回くらいの頻度でリリースされるくらいメンテナンスは頻繁に行われています。
(機能の性質上、日本でしか需要が見込めないと思われるのにありがたい限りです✨)
都道府県名と都道府県コードが両方返ってくるので、
都道府県名を入力する欄がテキスト入力でもプルダウンでも使いやすいと言えます。
※プルダウンで選ばせる形式のフォームの方が多い印象はありますが、、、
都道府県名がテキスト入力の場合のサンプルコード
やっていることは以下の通りです。
- 郵便番号と住所(都道府県・市区町村・町域)をstateで管理
- 郵便番号が7桁になった時点で
getAddress()が呼ばれる -
getAddress()で情報を取得できた場合は、住所の入力欄に自動入力される
また、スタイルはtailwindCSSを使って適用しています。
import { getAddress } from "jposta";
import { useState, useEffect } from "react";
export default function App() {
const [zip1, setZip1] = useState("");
const [zip2, setZip2] = useState("");
const [address, setAddress] = useState({
pref: "",
city: "",
area: "",
});
const fullZip = zip1 + zip2;
useEffect(() => {
const fetchAddress = async () => {
if (zip1.length === 3 && zip2.length === 4) {
try {
const res = await getAddress(fullZip);
if (res) {
setAddress({
pref: res.pref,
city: res.city,
area: res.area ?? "",
});
}
} catch (err) {
// 最悪、手動で打ち込めばなんとかなる箇所なので、通知は不要
}
}
};
fetchAddress();
}, [fullZip, zip1, zip2]);
return (
<div className="max-w-md mx-auto p-6 mt-10 bg-white rounded-xl shadow-md">
<h1 className="text-2xl font-bold mb-4 text-center">
郵便番号から住所自動入力
</h1>
<div className="mb-4">
<label className="block text-gray-700 mb-1">郵便番号</label>
<div className="flex gap-2">
<input
type="text"
value={zip1}
onChange={(e) =>
setZip1(e.target.value.replace(/\D/, "").slice(0, 3))
}
placeholder="例: 100"
className="w-20 px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:ring-blue-500 focus:border-blue-500"
/>
<span className="self-center">-</span>
<input
type="text"
value={zip2}
onChange={(e) =>
setZip2(e.target.value.replace(/\D/, "").slice(0, 4))
}
placeholder="0001"
className="w-24 px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:ring-blue-500 focus:border-blue-500"
/>
</div>
</div>
<div className="space-y-4">
<div>
<label className="block text-gray-700 mb-1">都道府県</label>
<input
type="text"
value={address.pref}
onChange={(e) => setAddress({ ...address, pref: e.target.value })}
className="w-full px-4 py-2 border border-gray-300 rounded-md shadow-sm focus:ring-blue-500 focus:border-blue-500"
/>
</div>
<div>
<label className="block text-gray-700 mb-1">市区町村</label>
<input
type="text"
value={address.city}
onChange={(e) => setAddress({ ...address, city: e.target.value })}
className="w-full px-4 py-2 border border-gray-300 rounded-md shadow-sm focus:ring-blue-500 focus:border-blue-500"
/>
</div>
<div>
<label className="block text-gray-700 mb-1">町域</label>
<input
type="text"
value={address.area}
onChange={(e) => setAddress({ ...address, area: e.target.value })}
className="w-full px-4 py-2 border border-gray-300 rounded-md shadow-sm focus:ring-blue-500 focus:border-blue-500"
/>
</div>
</div>
</div>
);
}
都道府県名をプルダウンで選ぶ場合のサンプルコード
以下のようになります。
stateで管理される値に都道府県コードが追加され、
プルダウンの自動入力の際は、都道府県コードで該当する都道府県名を検索しています。
import { getAddress } from "jposta";
import { useState, useEffect } from "react";
// 都道府県コード順に並んだリスト
const PREFECTURES = [
{ code: 1, name: "北海道" },
{ code: 2, name: "青森県" },
{ code: 3, name: "岩手県" },
{ code: 4, name: "宮城県" },
{ code: 5, name: "秋田県" },
{ code: 6, name: "山形県" },
{ code: 7, name: "福島県" },
{ code: 8, name: "茨城県" },
{ code: 9, name: "栃木県" },
{ code: 10, name: "群馬県" },
{ code: 11, name: "埼玉県" },
{ code: 12, name: "千葉県" },
{ code: 13, name: "東京都" },
{ code: 14, name: "神奈川県" },
{ code: 15, name: "新潟県" },
{ code: 16, name: "富山県" },
{ code: 17, name: "石川県" },
{ code: 18, name: "福井県" },
{ code: 19, name: "山梨県" },
{ code: 20, name: "長野県" },
{ code: 21, name: "岐阜県" },
{ code: 22, name: "静岡県" },
{ code: 23, name: "愛知県" },
{ code: 24, name: "三重県" },
{ code: 25, name: "滋賀県" },
{ code: 26, name: "京都府" },
{ code: 27, name: "大阪府" },
{ code: 28, name: "兵庫県" },
{ code: 29, name: "奈良県" },
{ code: 30, name: "和歌山県" },
{ code: 31, name: "鳥取県" },
{ code: 32, name: "島根県" },
{ code: 33, name: "岡山県" },
{ code: 34, name: "広島県" },
{ code: 35, name: "山口県" },
{ code: 36, name: "徳島県" },
{ code: 37, name: "香川県" },
{ code: 38, name: "愛媛県" },
{ code: 39, name: "高知県" },
{ code: 40, name: "福岡県" },
{ code: 41, name: "佐賀県" },
{ code: 42, name: "長崎県" },
{ code: 43, name: "熊本県" },
{ code: 44, name: "大分県" },
{ code: 45, name: "宮崎県" },
{ code: 46, name: "鹿児島県" },
{ code: 47, name: "沖縄県" },
];
export default function App() {
const [zip1, setZip1] = useState("");
const [zip2, setZip2] = useState("");
const [address, setAddress] = useState({
pref: "",
prefNum: 0,
city: "",
area: "",
});
const fullZip = zip1 + zip2;
useEffect(() => {
const fetchAddress = async () => {
if (zip1.length === 3 && zip2.length === 4) {
try {
const res = await getAddress(fullZip);
if (res) {
setAddress({
pref: res.pref,
prefNum: res.prefNum,
city: res.city,
area: res.area ?? "",
});
}
} catch (err) {
// 通知不要
}
}
};
fetchAddress();
}, [zip1, zip2, fullZip]);
return (
<div className="max-w-md mx-auto p-6 mt-10 bg-white rounded-xl shadow-md">
<h1 className="text-2xl font-bold mb-4 text-center">
郵便番号から住所自動入力
</h1>
<div className="mb-4">
<label className="block text-gray-700 mb-1">郵便番号</label>
<div className="flex gap-2">
<input
type="text"
value={zip1}
onChange={(e) =>
setZip1(e.target.value.replace(/\D/, "").slice(0, 3))
}
placeholder="例: 100"
className="w-20 px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:ring-blue-500 focus:border-blue-500"
/>
<span className="self-center">-</span>
<input
type="text"
value={zip2}
onChange={(e) =>
setZip2(e.target.value.replace(/\D/, "").slice(0, 4))
}
placeholder="0001"
className="w-24 px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:ring-blue-500 focus:border-blue-500"
/>
</div>
</div>
<div className="space-y-4">
<div>
<label className="block text-gray-700 mb-1">都道府県</label>
<select
value={address.prefNum}
onChange={(e) => {
const selectedCode = parseInt(e.target.value, 10);
const selectedPref = PREFECTURES.find(
(p) => p.code === selectedCode
);
if (selectedPref) {
setAddress({
...address,
pref: selectedPref.name,
prefNum: selectedPref.code,
});
}
}}
className="w-full px-4 py-2 border border-gray-300 rounded-md shadow-sm focus:ring-blue-500 focus:border-blue-500"
>
<option value={0}>選択してください</option>
{PREFECTURES.map((pref) => (
<option key={pref.code} value={pref.code}>
{pref.name}
</option>
))}
</select>
</div>
<div>
<label className="block text-gray-700 mb-1">市区町村</label>
<input
type="text"
value={address.city}
onChange={(e) => setAddress({ ...address, city: e.target.value })}
className="w-full px-4 py-2 border border-gray-300 rounded-md shadow-sm focus:ring-blue-500 focus:border-blue-500"
/>
</div>
<div>
<label className="block text-gray-700 mb-1">町域</label>
<input
type="text"
value={address.area}
onChange={(e) => setAddress({ ...address, area: e.target.value })}
className="w-full px-4 py-2 border border-gray-300 rounded-md shadow-sm focus:ring-blue-500 focus:border-blue-500"
/>
</div>
</div>
</div>
);
}
Discussion