ホーム
おまかせ表示
最近の更新
特別ページ
コミュニティ・ポータル
設定
MochiuWiki : SUSE, EC, PCBについて
免責事項
MochiuWiki : SUSE, EC, PCB
検索
利用者メニュー
ログイン
Reactの基礎 - フォーム(制御コンポーネント)のソースを表示
←
Reactの基礎 - フォーム(制御コンポーネント)
📢 Webサイト閉鎖と移転のお知らせ
このWebサイトは
2026年9月に閉鎖
いたします。
移転先は
https://mochiu.net/index.php?title=Reactの基礎 - フォーム(制御コンポーネント)
です。
新しい記事は移転先で追加しております。
(旧サイトでは記事を追加しておりません)
あなたには「このページの編集」を行う権限がありません。理由は以下の通りです:
この操作は、次のグループのいずれかに属する利用者のみが実行できます:
管理者
、new-group。
このページのソースの閲覧やコピーができます。
== 概要 == Reactにおける制御コンポーネント (Controlled Component) は、フォーム要素の値をReactの状態 (state) により管理するパターンである。<br> HTMLのネイティブフォーム要素はデフォルトで独自の内部状態を保持するが、制御コンポーネントではその状態をReactの <code>useState</code> フックが一元管理する。<br> <br> 制御コンポーネントの核心は、<code>value</code> プロパティと <code>onChange</code> ハンドラを組み合わせたパターンにある。<br> <br> フォーム要素の <code>value</code> には状態変数を渡し、ユーザの入力を <code>onChange</code> イベントで検知して状態を更新する。<br> この仕組みにより、Reactの状態がフォーム値の「信頼できる唯一の情報源 (Single Source of Truth)」となる。<br> <br> <center> {| class="wikitable" |+ 状態管理の流れ ! 番号 !! 内容 |- | 1 || ユーザがフォーム要素に入力する。 |- | 2 || <code>onChange</code> イベントが発生する。 |- | 3 || イベントハンドラが新しい値を取得して、<code>setState</code> で状態を更新する。 |- | 4 || Reactが再レンダリングを実行する。 |- | 5 || 新しい状態値が、<code>value</code> プロパティ経由でフォーム要素に反映される。 |} </center> <br> Single Source of Truthを維持することにより、バリデーション、条件付きのボタン無効化、フォーム値の外部API送信等が直感的に実装できるようになる。<br> また、React DevToolsを通じたデバッグや状態のトレースも容易になる。<br> <br> なお、<code>value</code> を渡す場合は、必ず同期的な <code>onChange</code> ハンドラが必要である。<br> <br> <u><code>onChange</code> 無しで <code>value</code> を指定すると、フォーム要素が読み取り専用になるため注意が必要である。</u><br> <br><br> == 制御コンポーネントの基本 == ==== value + onChangeパターン ==== 制御コンポーネントの基本構造を以下に示す。<br> <code>useState</code> で状態変数を定義して、その値を <code>value</code> に渡し、<code>onChange</code> で状態を更新する。<br> <br> <syntaxhighlight lang="typescript"> import { useState } from 'react'; export default function BasicControlledInput() { const [name, setName] = useState<string>(''); const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => { setName(e.target.value); }; return ( <div> <input type="text" value={name} onChange={handleChange} placeholder="名前を入力" /> <p>入力値: {name}</p> </div> ); } </syntaxhighlight> <br> インラインハンドラを使用するとより簡潔に記述できる。<br> <u>TypeScriptはコンテキストからイベント型を推論するため、インラインの場合は型注釈を省略できる。</u><br> <br> <syntaxhighlight lang="typescript"> <input value={name} onChange={e => setName(e.target.value)} /> </syntaxhighlight> <br> ==== 状態管理の流れ ==== 下図に、制御コンポーネントにおける状態管理のサイクルを示す。<br> <br> [[ファイル:React Control Components StateManagementCycle 1.png|中央]] <br> <u>この一方向データフロー (Unidirectional Data Flow) により、アプリケーションの状態変化を追跡しやすくなる。</u><br> <br> ただし、非制御コンポーネントではDOMが直接値を保持するため、この追跡が困難になる。<br> <br><br> == テキスト入力 == ==== input要素 ==== <code>input</code> 要素の制御コンポーネント化の基本形を以下に示す。<br> TypeScriptでは <code>React.ChangeEvent<HTMLInputElement></code> 型を使用してイベントハンドラを型付けする。<br> <br> <syntaxhighlight lang="typescript"> import { useState } from 'react'; export default function TextInputExample() { const [text, setText] = useState<string>(''); const [email, setEmail] = useState<string>(''); const [password, setPassword] = useState<string>(''); const [age, setAge] = useState<number>(0); return ( <div> <input type="text" value={text} onChange={(e: React.ChangeEvent<HTMLInputElement>) => setText(e.target.value)} placeholder="テキスト入力" /> <input type="email" value={email} onChange={e => setEmail(e.target.value)} placeholder="メールアドレス" /> <input type="password" value={password} onChange={e => setPassword(e.target.value)} placeholder="パスワード" /> <input type="number" value={age} onChange={e => setAge(parseInt(e.target.value) || 0)} placeholder="年齢" /> </div> ); } </syntaxhighlight> <br> <u><code>input type="number"</code> の場合、<code>e.target.value</code> は常に文字列として返される点に注意が必要である。</u><br> <br> 数値として扱う場合は <code>parseInt()</code> または <code>parseFloat()</code> で明示的に変換する。<br> <u>ただし、空文字列が入力されると <code>parseInt('')</code> は <code>NaN</code> になるため、<code>|| 0</code> のようなデフォルト値の処理が必要である。</u><br> <br> ==== textarea要素 ==== Reactの <code>textarea</code> 要素はHTMLとは異なる点がある。<br> HTMLでは <code><textarea>初期値</textarea></code> と子要素で値を指定するが、Reactでは <code>value</code> プロパティで管理する。<br> <br> <syntaxhighlight lang="typescript"> import { useState } from 'react'; export default function TextAreaExample() { const [content, setContent] = useState<string>(''); return ( <textarea value={content} onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) => setContent(e.target.value)} placeholder="内容を入力してください" rows={4} cols={40} /> ); } </syntaxhighlight> <br> 下表に、HTMLとReactの <code>textarea</code> の違いを示す。<br> <br> <center> {| class="wikitable" |+ textareaのHTMLとReactの違い ! 項目 !! HTML !! React |- | 初期値の指定 || <code><textarea>初期値</textarea></code> || <code>defaultValue="初期値"</code> または状態管理 |- | 子要素 || children として指定可能 || children は受け入れない (value プロパティのみ) |- | 値の制御 || DOM が直接保持 || 状態変数経由で管理 |- | イベント型 || - || <code>React.ChangeEvent<HTMLTextAreaElement></code> |} </center> <br><br> == セレクトボックス == ==== 単一選択 ==== <code>select</code> 要素の制御コンポーネント化では、HTMLの <code><option selected></code> 属性は使用しない。<br> 代わりに、<code>select</code> 要素の <code>value</code> プロパティで選択状態を管理する。<br> <br> <syntaxhighlight lang="typescript"> import { useState } from 'react'; export default function SingleSelectExample() { const [selectedFruit, setSelectedFruit] = useState<string>('orange'); return ( <div> <select value={selectedFruit} onChange={(e: React.ChangeEvent<HTMLSelectElement>) => setSelectedFruit(e.target.value)} > <option value="">-- 選択してください --</option> <option value="apple">りんご</option> <option value="banana">バナナ</option> <option value="orange">オレンジ</option> </select> <p>選択中: {selectedFruit}</p> </div> ); } </syntaxhighlight> <br> ==== 複数選択 ==== <code>multiple</code> 属性を付与すると複数の選択が可能になる。<br> 選択された値は配列として状態管理して、<code>Array.from(e.target.selectedOptions)</code> で選択項目の配列を取得する。<br> <br> <syntaxhighlight lang="typescript"> import { useState } from 'react'; export default function MultipleSelectExample() { const [selectedVegetables, setSelectedVegetables] = useState<string[]>(['corn', 'tomato']); const handleChange = (e: React.ChangeEvent<HTMLSelectElement>) => { const selectedOptions = Array.from(e.target.selectedOptions); const values = selectedOptions.map(option => option.value); setSelectedVegetables(values); }; return ( <div> <select multiple={true} value={selectedVegetables} onChange={handleChange} > <option value="corn">トウモロコシ</option> <option value="tomato">トマト</option> <option value="cucumber">キュウリ</option> <option value="broccoli">ブロッコリー</option> </select> <p>選択中: {selectedVegetables.join(', ')}</p> </div> ); } </syntaxhighlight> <br> <code>e.target.selectedOptions</code> は、<code>HTMLCollection</code> 型であるため、そのままでは配列メソッドが使用できない。<br> <code>Array.from()</code> でネイティブ配列に変換してから <code>map()</code> で値を取り出す。<br> <br><br> == チェックボックス == ==== 単一チェックボックス ==== チェックボックスは <code>value</code> ではなく <code>checked</code> プロパティで管理する。<br> <code>onChange</code> では <code>e.target.checked</code> (boolean) で値を取得する。<br> <br> <syntaxhighlight lang="typescript"> import { useState } from 'react'; export default function SingleCheckboxExample() { const [isAgreed, setIsAgreed] = useState<boolean>(false); return ( <div> <label> <input type="checkbox" checked={isAgreed} onChange={(e: React.ChangeEvent<HTMLInputElement>) => setIsAgreed(e.target.checked)} /> 利用規約に同意します </label> <p>同意状態: {isAgreed ? '同意済み' : '未同意'}</p> </div> ); } </syntaxhighlight> <br> ==== 複数チェックボックスの管理 ==== 複数のチェックボックスを管理する場合は、配列の状態で選択済みの値を保持する。<br> チェック時は配列に値を追加し、チェック解除時は配列から値を削除するトグルロジックを実装する。<br> <br> <syntaxhighlight lang="typescript"> import { useState } from 'react'; interface CheckboxItem { id: string; label: string; value: string; } export default function MultipleCheckboxExample() { const [selectedTags, setSelectedTags] = useState<string[]>([]); const tags: CheckboxItem[] = [ { id: 'react', label: 'React', value: 'react' }, { id: 'typescript', label: 'TypeScript', value: 'typescript' }, { id: 'nodejs', label: 'Node.js', value: 'nodejs' }, { id: 'graphql', label: 'GraphQL', value: 'graphql' }, ]; const handleCheckboxChange = (e: React.ChangeEvent<HTMLInputElement>) => { const { value, checked } = e.target; setSelectedTags(prev => { if (checked) { // チェックされた場合、配列に追加 return [...prev, value]; } else { // チェック解除された場合、配列から削除 return prev.filter(tag => tag !== value); } }); }; return ( <fieldset> <legend>タグを選択してください</legend> {tags.map(tag => ( <label key={tag.id}> <input type="checkbox" value={tag.value} checked={selectedTags.includes(tag.value)} onChange={handleCheckboxChange} /> {tag.label} </label> ))} <p>選択済み: {selectedTags.join(', ') || 'なし'}</p> </fieldset> ); } </syntaxhighlight> <br> 各チェックボックスに対して個別のトグルハンドラを用意する方法も有効である。<br> <br> <syntaxhighlight lang="typescript"> const handleToggle = (tagValue: string) => { setSelectedTags(prev => { if (prev.includes(tagValue)) { return prev.filter(tag => tag !== tagValue); } else { return [...prev, tagValue]; } }); }; // 使用例 <input type="checkbox" checked={selectedTags.includes('react')} onChange={() => handleToggle('react')} /> </syntaxhighlight> <br><br> == ラジオボタン == ==== ラジオグループ ==== ラジオボタンのグループは、同じ <code>name</code> 属性を持つ複数の <code>input type="radio"</code> 要素で構成する。<br> 選択状態は文字列の状態で管理し、<code>checked</code> プロパティで各ラジオボタンの選択状態を反映する。<br> <br> <syntaxhighlight lang="typescript"> import { useState } from 'react'; interface RadioOption { value: string; label: string; } export default function RadioGroupExample() { const [selectedRole, setSelectedRole] = useState<string>('user'); const roles: RadioOption[] = [ { value: 'user', label: 'ユーザ' }, { value: 'admin', label: '管理者' }, { value: 'guest', label: 'ゲスト' }, ]; return ( <fieldset> <legend>ロールを選択してください</legend> {roles.map(role => ( <label key={role.value}> <input type="radio" name="role" value={role.value} checked={selectedRole === role.value} onChange={(e: React.ChangeEvent<HTMLInputElement>) => setSelectedRole(e.target.value)} /> {role.label} </label> ))} <p>選択中のロール: {selectedRole}</p> </fieldset> ); } </syntaxhighlight> <br> 同じ <code>name</code> 属性を持つラジオボタンは自動的に1つのグループを形成する。<br> ユーザが1つを選択すると、同じ <code>name</code> を持つ他のラジオボタンは自動的に非選択になる。<br> <br> TypeScriptでリテラル型を使用する場合は以下に示すように型を定義する。<br> <br> <syntaxhighlight lang="typescript"> type Role = 'user' | 'admin' | 'guest'; const [selectedRole, setSelectedRole] = useState<Role>('user'); // onChangeでasアサーションを使用 onChange={(e) => setSelectedRole(e.target.value as Role)} </syntaxhighlight> <br><br> == 複数フィールドの一括管理 == ==== オブジェクトstateによる管理 ==== フォームに複数のフィールドが存在する場合、フィールドごとに個別の状態変数を用意するよりも、オブジェクトの状態として一括管理する方が効率的である。<br> <code>interface</code> でフォームデータの型を定義し、<code>useState<FormData></code> で初期値を設定する。<br> <br> <syntaxhighlight lang="typescript"> import { useState } from 'react'; interface FormData { username: string; email: string; age: number; country: string; newsletter: boolean; } export default function MultiFieldFormExample() { const [formData, setFormData] = useState<FormData>({ username: '', email: '', age: 0, country: '', newsletter: false, }); return ( <form> <input type="text" name="username" value={formData.username} onChange={/* 次のセクションで定義する汎用ハンドラを使用 */} placeholder="ユーザ名" /> {/* 他のフィールドも同様 */} </form> ); } </syntaxhighlight> <br> ==== 汎用onChangeハンドラ ==== <code>name</code> 属性を活用した汎用ハンドラを定義することで、各フィールドに個別のハンドラを用意する必要がなくなる。<br> <code>e.target.name</code> でフィールド名を取得し、<code>[name]: value</code> の動的プロパティアクセスで該当フィールドのみを更新する。<br> <br> <syntaxhighlight lang="typescript"> import { useState } from 'react'; interface FormData { username: string; email: string; age: number; country: string; newsletter: boolean; } export default function MultiFieldFormExample() { const [formData, setFormData] = useState<FormData>({ username: '', email: '', age: 0, country: '', newsletter: false, }); const handleChange = ( e: React.ChangeEvent<HTMLInputElement | HTMLSelectElement> ) => { const { name, type, value } = e.target; if (type === 'checkbox') { // チェックボックスは、e.target.checkedを使用 const checked = (e.target as HTMLInputElement).checked; setFormData(prev => ({ ...prev, [name]: checked, })); } else if (type === 'number') { // number型は数値に変換する setFormData(prev => ({ ...prev, [name]: parseInt(value) || 0, })); } else { // text, email, select等は文字列のまま setFormData(prev => ({ ...prev, [name]: value, })); } }; const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => { e.preventDefault(); console.log('送信データ:', formData); }; return ( <form onSubmit={handleSubmit}> <input type="text" name="username" value={formData.username} onChange={handleChange} placeholder="ユーザ名" /> <input type="email" name="email" value={formData.email} onChange={handleChange} placeholder="メールアドレス" /> <input type="number" name="age" value={formData.age} onChange={handleChange} placeholder="年齢" /> <select name="country" value={formData.country} onChange={handleChange} > <option value="">-- 選択 --</option> <option value="japan">日本</option> <option value="usa">米国</option> <option value="uk">イギリス</option> </select> <label> <input type="checkbox" name="newsletter" checked={formData.newsletter} onChange={handleChange} /> ニュースレター購読 </label> <button type="submit">送信</button> </form> ); } </syntaxhighlight> <br> 下表に、スプレッド構文 <code>{...prev, [name]: value}</code> による状態更新のパターンをまとめる。<br> <br> <center> {| class="wikitable" |+ スプレッド構文による状態更新パターン ! パターン !! 用途 !! サンプルコード |- | 単一フィールド更新 || 1つのフィールドのみ更新 || <u>setForm(prev => ({...prev, [name]: value}))</u> |- | 複数フィールド同時更新 || 複数フィールドを一度に更新 || <u>setForm(prev => ({...prev, field1: val1, field2: val2}))</u> |- | ネストオブジェクト更新 || ネストした構造の一部を更新 || <u>setForm(prev => ({...prev, address: {...prev.address, city: newCity}}))</u> |} </center> <br><br> == フォームのバリデーション == ==== リアルタイムバリデーション ==== <code>onChange</code> が発生するたびにバリデーションを実行し、エラーメッセージをリアルタイムに表示するパターンを示す。<br> バリデーション関数を独立して定義することで、テストや再利用が容易になる。<br> <br> <syntaxhighlight lang="typescript"> import { useState } from 'react'; interface FormData { username: string; email: string; password: string; } interface FormErrors { [key: string]: string; } const validateForm = (data: FormData): FormErrors => { const errors: FormErrors = {}; if (data.username.length < 3) { errors.username = 'ユーザ名は3文字以上である必要があります'; } const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; if (!emailRegex.test(data.email)) { errors.email = '有効なメールアドレスを入力してください'; } if (data.password.length < 8) { errors.password = 'パスワードは8文字以上である必要があります'; } return errors; }; export default function RealtimeValidationForm() { const [formData, setFormData] = useState<FormData>({ username: '', email: '', password: '', }); const [errors, setErrors] = useState<FormErrors>({}); const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => { const { name, value } = e.target; setFormData(prev => { const updated = { ...prev, [name]: value }; // 状態更新と同時にバリデーションを実行 const newErrors = validateForm(updated); setErrors(newErrors); return updated; }); }; return ( <form> <div> <input type="text" name="username" value={formData.username} onChange={handleChange} placeholder="ユーザ名" /> {errors.username && ( <p style={{ color: 'red' }}>{errors.username}</p> )} </div> <div> <input type="email" name="email" value={formData.email} onChange={handleChange} placeholder="メール" /> {errors.email && <p style={{ color: 'red' }}>{errors.email}</p>} </div> <div> <input type="password" name="password" value={formData.password} onChange={handleChange} placeholder="パスワード" /> {errors.password && ( <p style={{ color: 'red' }}>{errors.password}</p> )} </div> </form> ); } </syntaxhighlight> <br> ==== 送信時バリデーション ==== 送信時にバリデーションを実行するパターンでは、<code>onSubmit</code> ハンドラ内で <code>e.preventDefault()</code> を呼び出してページのリロードを防ぎ、バリデーションを実行する。<br> また、<code>touched</code> 状態を管理することにより、ユーザが1度もフォーカスしていないフィールドにはエラーを表示しないよう制御できる。<br> <br> <syntaxhighlight lang="typescript"> import { useState } from 'react'; interface FormData { username: string; email: string; password: string; } interface FormErrors { [key: string]: string; } export default function SubmitValidationForm() { const [formData, setFormData] = useState<FormData>({ username: '', email: '', password: '', }); const [errors, setErrors] = useState<FormErrors>({}); // touched: ユーザが一度フォーカスしたフィールドを記録する const [touched, setTouched] = useState<{ [key: string]: boolean }>({}); const validateForm = (data: FormData): FormErrors => { const errors: FormErrors = {}; if (!data.username.trim()) { errors.username = 'ユーザ名は必須です'; } else if (data.username.length < 3) { errors.username = 'ユーザ名は3文字以上である必要があります'; } if (!data.email.trim()) { errors.email = 'メールアドレスは必須です'; } else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(data.email)) { errors.email = '有効なメールアドレスを入力してください'; } if (!data.password) { errors.password = 'パスワードは必須です'; } else if (data.password.length < 8) { errors.password = 'パスワードは8文字以上である必要があります'; } return errors; }; const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => { const { name, value } = e.target; setFormData(prev => ({ ...prev, [name]: value })); }; // フォーカスが外れた時点でtouchedを更新 const handleBlur = (e: React.FocusEvent<HTMLInputElement>) => { const { name } = e.target; setTouched(prev => ({ ...prev, [name]: true })); }; const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => { // ページのリロードを防ぐ e.preventDefault(); const newErrors = validateForm(formData); setErrors(newErrors); if (Object.keys(newErrors).length === 0) { // バリデーション通過時にAPIへ送信 console.log('フォーム送信:', formData); } }; return ( <form onSubmit={handleSubmit}> <div> <input type="text" name="username" value={formData.username} onChange={handleChange} onBlur={handleBlur} placeholder="ユーザ名" /> {/* touchedかつエラーがある場合のみ表示 */} {touched.username && errors.username && ( <p style={{ color: 'red' }}>{errors.username}</p> )} </div> <div> <input type="email" name="email" value={formData.email} onChange={handleChange} onBlur={handleBlur} placeholder="メール" /> {touched.email && errors.email && ( <p style={{ color: 'red' }}>{errors.email}</p> )} </div> <div> <input type="password" name="password" value={formData.password} onChange={handleChange} onBlur={handleBlur} placeholder="パスワード" /> {touched.password && errors.password && ( <p style={{ color: 'red' }}>{errors.password}</p> )} </div> <button type="submit" disabled={Object.keys(errors).length > 0} > 送信 </button> </form> ); } </syntaxhighlight> <br> 下表に、バリデーションパターンを示す。<br> <br> <center> {| class="wikitable" |+ バリデーションパターンの比較 ! パターン !! タイミング !! メリット !! デメリット |- | リアルタイムバリデーション || onChange発生時 || 即時フィードバックで入力ミスをすぐに検知できる || 入力途中でもエラーが表示されるため、UXが低下する場合がある |- | 送信時バリデーション || onSubmit発生時 || 入力が完了してからエラーを表示するためUXが向上する || エラーに気付くのが遅くなる |- | touched + onBlur || フォーカスが外れた時 || フィールドを離れた後にのみエラーを表示する<br>現実的なUX || touched状態の管理が必要になる |} </center> <br><br> == 関連情報 == * [[Reactの基礎 - TSXの基本構文]] * [[Reactの基礎 - コンポーネント]] * [[Reactの基礎 - PropsとChildren]] * [[Reactの基礎 - イベントハンドリング]] * [[Reactの基礎 - フォーム(非制御コンポーネント)]] <br><br> {{#seo: |title={{PAGENAME}} : Exploring Electronics and SUSE Linux | MochiuWiki |keywords=MochiuWiki,Mochiu,Wiki,Mochiu Wiki,Electric Circuit,Electric,pcb,Mathematics,AVR,TI,STMicro,AVR,ATmega,MSP430,STM,Arduino,Xilinx,FPGA,Verilog,HDL,PinePhone,Pine Phone,Raspberry,Raspberry Pi,C,C++,C#,Qt,Qml,MFC,Shell,Bash,Zsh,Fish,SUSE,SLE,Suse Enterprise,Suse Linux,openSUSE,open SUSE,Leap,Linux,uCLnux,電気回路,電子回路,基板,プリント基板,React,JavaScript,TypeScript,TSX,フォーム,制御コンポーネント,Controlled Component,useState,onChange,value,checked,バリデーション,input,textarea,select,checkbox,radio,Single Source of Truth,イベントハンドラ,ChangeEvent |description={{PAGENAME}} - 電子回路とSUSE Linuxに関する情報 | This page is {{PAGENAME}} in our wiki about electronic circuits and SUSE Linux |image=/resources/assets/MochiuLogo_Single_Blue.png }} __FORCETOC__ [[カテゴリ:Rust]][[カテゴリ:Web]]
Reactの基礎 - フォーム(制御コンポーネント)
に戻る。