[JavaScript]フォームの中身をJSONに変換する
こんにちは。タカギです。
今回は小ネタです。
HTMLのフォーム入力値をお手軽にJSONに変換してAPIコールする方法について、素のHTML+JSのパターン・Reactを使ったパターンの2通りを紹介します。
※ あまり推奨されるコードは出てきません
※ 簡略化のため、CSRF対策やバリデーションは省略します
素のHTMLの場合
例えばこんなフォームがあったとします。
<h1>お問合せフォーム</h1>
<form id="inquiry">
<label>
お名前
<input type="text" name="name">
</label>
<label>
メールアドレス
<input type="email" name="email">
</label>
<label>
お問合せ内容
<textarea name="content"></textarea>
</label>
<button type="submit">送信</button>
</form>
このフォームを単にHTMLでサブミットすれば良いというのであれば話は簡単ですが、
そうではなくこの入力値をJSONに変換しバックエンドAPIをコールする必要がある場合があります。
その場合、JavaScriptのFormDataを使用してこのように書けます。
/**
* @type {HTMLFormElement}
*/
const form = document.getElementById("inquiry");
form.addEventListener("submit", async ev => {
ev.preventDefault();
const formData = new FormData(form);
const data = Object.fromEntries(formData.entries());
console.log(data); // {name: 'test', email: 'test@example.com', content: 'xxx'}
try {
// APIコール
await window.fetch("/", {
method: "POST",
body: JSON.stringify(data),
});
window.alert("送信しました。");
// 完了時に入力値をクリア
form.reset();
} catch (e) {
console.log(e);
}
});
HTMLFormElementからFormDataを生成し、FormData.entries()とObject.fromEntries()を使ってオブジェクトに変換できるのがポイントです。
各入力項目を1つずつ手作業で抜き出すことなく、一発でJSONに変換できて便利です。
Reactの場合
同じことがReactでも行えます。
import { FormEvent } from "react";
export default function ExampleForm() {
const handleSubmit = async (ev: FormEvent<HTMLFormElement>) => {
const form = ev.target as HTMLFormElement;
const formData = new FormData(form);
const data = Object.fromEntries(formData.entries());
console.log(data);
try {
// APIコール
await window.fetch("/", {
method: "POST",
body: JSON.stringify(data),
});
window.alert("送信しました。");
// 完了時に入力値をクリア
form.reset();
} catch (e) {
console.log(e);
}
};
return (
<>
<h1>お問合せフォーム</h1>
<form onSubmit={handleSubmit}>
<label>
お名前
<input type="text" name="name" />
</label>
<label>
メールアドレス
<input type="email" name="email" />
</label>
<label>
お問合せ内容
<textarea name="content" />
</label>
<button type="submit">送信</button>
</form>
</>
);
}
これはかなり見慣れない書き方だと思いますし、あまり推奨できる書き方ではありません。 React.useState()を使ってReactで状態管理をせず、生のDOMに状態を持たせています。
React.useState()を使いReactのstateを“信頼できる唯一の情報源 (single source of truth)”とするのを「制御されたコンポーネント(controlled component)」と呼ぶのに対し、
今回のような生DOMに状態を持たせるコンポーネントを「非制御コンポーネント(uncontrolled component)」と呼びます。
一般的には可能な限りcontrolled componentで書くことが推奨されていると思いますが、React公式ドキュメントでも
制御されたコンポーネントは、あらゆる種類のデータの変更に対してイベントハンドラを書き、あらゆる入力状態を React コンポーネントに通してやる必要があるため、時としてうんざりすることがあります。このことは既存のコードベースを React に変換する場合や、React アプリケーションを非 React のライブラリと統合する場合に、特に問題化します。これらの状況においては、入力フォームを実装する代替手段である非制御コンポーネントを検討してみてください。
と言っています。
うんざりしている時であれば使用も許されるそうなので、選択肢の一つとして頭に入れておいても良いかもしれません。
生DOMに状態を持つため入力値をJavaScriptから扱うことがやや面倒になりReactの恩恵はほぼ受けられませんが、簡単なクライアントバリデーションはHTML5でできますし、入力値のクリア、完了メッセージの表示などもReactの機能は使わずに生JSのみで実現しています。
要件次第ではこの書き方+α程度で十分なケースもあるかもしれません。
(個人的な観測範囲では、そこそこある印象です。)
ここまで読んでいて気づいた方がほとんどだと思いますが、これで十分なケース(このレベルの単純なフォーム)ではReactを導入することで得られるメリットはほぼありません。
適切な技術選定、していきたいですね。
まとめ
- FormData.entries()とObject.fromEntries()でフォーム入力値をJSONに変換できてちょっと便利
- 非制御コンポーネント(uncontrolled component)というReactを頑張らない選択肢
- 単純なフォームであればReactを導入する必要がない(こともある)
フレームワークを使わずにフォームを実装する試みは別記事でも紹介しておりますので、よろしければそちらも見ていただければと思います。
ここまでお読みいただきありがとうございました。