はじめに
Reactでフォーム画面を実装するためには制御コンポーネントか、非制御コンポーネントを理解しておく必要があります。
両者はフォームデータの管理が異なります。
制御コンポーネントはReactのstateで管理することであり、非制御コンポーネントはDOM自身が管理します。
Reactのドキュメントでは、制御コンポーネントを使用することが推奨されています。
では実装にどのような違いがあるかを確認していきましょう。
環境
React 18.2.0
制御コンポーネント
<input />
要素等に初期値を渡すだけでは、値が現在どの状態かを制御することができません。
UIによっては複数の方法でフォームのデータを更新、状態によってスタイルを変更したい等のケースが発生すると思います。
例えば、郵便番号を入力すると住所が自動入力される、規約をスクロールすると同意チェックされる、入力中の要素が分かるようにスタイルを変更するなどです。
それらの状態をReactのstateで管理することを制御されたコンポーネントといいます。
例として、商品の注文フォームを制御されたコンポーネントで実装していきます。
import { useState } from "react";
const Products = [
{
name: "apple",
label: "りんご",
price: 100
},
{
name: "banana",
label: "バナナ",
price: 150
},
{
name: "orange",
label: "オレンジ",
price: 200
}
];
export const Form = () => {
const [formData, setFormData] = useState({
apple: 0,
banana: 0,
orange: 0
});
const onSubmitHandler = (event) => {
event.preventDefault();
};
const onChangeHandler = (name) => (event) => {
switch (name) {
case "apple": {
setFormData({ ...formData, apple: Number(event.target.value) });
break;
}
case "banana": {
setFormData({ ...formData, banana: Number(event.target.value) });
break;
}
case "orange": {
setFormData({ ...formData, orange: Number(event.target.value) });
break;
}
default:
break;
}
};
return (
<form
style={{ marginLeft: "5px" }}
method="post"
onSubmit={onSubmitHandler}
>
<h2>注文画面</h2>
{Products.map((product) => (
<div key={product.name}>
<label>
<p>
{product.label} : {product.price}円
</p>
<input
style={{ width: "30px", textAlign: "end" }}
type="number"
min="0"
defaultValue="0"
onChange={onChangeHandler(product.name)}
/>{" "}
個 {product.price * formData[product.name]} 円
</label>
</div>
))}
<p>
合計金額 :{" "}
{Products.map(
(product) => product.price * formData[product.name]
).reduce((sum, price) => price + sum, 0)}
円
</p>
<button type="submit">送信</button>
</form>
);
};
実装方法はともかくとして、ここでは商品の数量を以下のようにStateで定義し管理するようにしています。
...
const [formData, setFormData] = useState({
apple: 0,
banana: 0,
orange: 0
});
...
そうすることで、現在の商品の数量状態を管理することでできるため、注文商品ごとの金額、注文商品全体の合計金額を表示することができました。
基本的にReactでのフォーム画面は制御コンポーネントで実装すると良いでしょう。
非制御コンポーネント
先ほどはReactのstateで状態を管理していましたが、DOM自身で管理する方法を非制御コンポーネントと言います。
例が雑ですが、下記のようにしてDOM自身の値を取得することができます。
import { useRef } from "react";
export const Ref = () => {
const input = useRef();
const onSubmitHandler = (event) => {
event.preventDefault();
console.log(input.current.value);
};
return (
<form
style={{ marginLeft: "5px" }}
method="post"
onSubmit={onSubmitHandler}
>
<input ref={input} />
<br />
<button type="submit">送信</button>
</form>
);
};
useRef()でRefオブジェクトを生成し、それをInput要素のref属性に渡しています。
値の取得は、input.current.value
で確認することができます。
自前で非制御コンポーネントでフォームを実装するケースは少ない気がしますが、外部ライブラリであるReact Hook Formではこちらの非制御コンポーネントが採用されています。
非制御コンポーネントのメリットとしては、Reactで書かれていないコードとの統合がしやすかったり、stateが変更されることによる再レンダリングが起きないため、パフォーマンスが良いとされています。しかし、非制御コンポーネントによる実装は要検討した方が良いでしょう。
最後に
React Hook Formが非推奨の非制御コンポーネントを採用しているとのことで、気になり調べてみました。非制御コンポーネントを採用するメリットは少なからずあり、外部ライブラリ等で使う分には問題ないかと個人的には感じました。Reactの流儀に反しているかどうかは難しく自分には分かりません...。
参考記事