制御コンポーネント (controlled components) React コンポーネントでフォームを構成する場合、コンポーネントの状態 (state
) に基づいて表示を行うように実装すると、フォームの表示内容を制御しやすくなります。
このように、コンポーネントの表示内容が、完全にその状態 (state
) によって決まるように実装されたものを、制御されたコンポーネント (controlled components) と呼びます。
このように設計することで、若干コード量は増えますが、表示内容を変更したいときはコンポーネントの state
を変更するだけで済むようになります。
例えば、ネットワークから取得したデータをフォームに表示するような場合、そのフォームの構造を知る必要はなく、単純に state
を更新するだけでよくなります。
これは、データとビューが分離された設計になっており、アプリ設計におけるベストプラクティスのひとつです。
下記は、<input type="text">
要素と <input type="submit">
要素を持つ MessageForm
コンポーネントの実装例です。
ユーザーがテキストを入力するたびに handleChange()
が呼び出され、コンポーネントの状態 (state
) が更新されます。
setState()
の呼び出しにより state
が変更が変更されると、再度 render()
が実行され、表示内容が state
の値に基づいて更新されます。
まずは、クラスコンポーネント形式での実装例。
components/MessageForm.tsx import * as React from 'react' ;
interface IState {
msg : string ;
}
// テキスト入力エリアと、submit ボタンを持つフォームを表示するコンポーネント
export class MessageForm extends React . Component < {}, IState > {
constructor ( props : {}) {
super ( props );
// ステートの初期化(最初は入力エリアは空っぽ)
this . state = { msg : '' };
}
// input 要素でのキー入力のたびに呼び出される
private handleChange = ( e : React.ChangeEvent < HTMLInputElement >) => {
// ステートを変更することで表示を更新する
this . setState ({ msg : e.target.value });
}
// submit ボタンを押したときに呼び出される
private handleSubmit = ( e : React.FormEvent ) => {
// submit ボタンのデフォルトの振る舞い (GET や POST) を抑制する
e . preventDefault ();
// 実際にはここでメッセージ送信を行う(内容は state から取得する)
alert ( '次のメッセージが送信されました: ' + this . state . msg );
}
render() {
return < form onSubmit = { this . handleSubmit }>
< label > メッセージ:
< input type = "text" value = { this . state . msg } onChange = { this . handleChange } />
</ label >
< input type = "submit" value = "送信" />
</ form >;
}
}
下記は、関数コンポーネントでの実装例です。
コメントを削ったというのもありますが、やはり関数コンポーネントにするとコードがすっきりしますね。
components/MessageForm.tsx import * as React from 'react' ;
export const MessageForm : React.FC = () => {
// ステート用のフック(テキストの初期値は空っぽ)
const [ message , setMessage ] = React . useState ( '' );
const handleChange = ( e : React.ChangeEvent < HTMLInputElement >) => {
setMessage ( e . target . value );
};
const handleSubmit = ( e : React.FormEvent ) => {
e . preventDefault ();
alert ( '次のメッセージが送信されました: ' + message );
};
return < form onSubmit = { handleSubmit }>
< label > メッセージ:
< input type = "text" value = { message } onChange = { handleChange } />
</ label >
< input type = "submit" value = "送信" />
</ form >;
};
上記のように作成した MessageForm
コンポーネントは次のように使用します(といってもポンっと置いてるだけですが)。
index.tsx(使用例) import * as React from 'react' ;
import * as ReactDOM from 'react-dom' ;
import { MessageForm } from './components/MessageForm' ;
ReactDOM . render (< MessageForm />, document . getElementById ( 'root' ));
他のフォーム要素の場合(textarea 要素など) 他の要素も同様に使える 他のフォーム要素を使う場合も、前述の例(<input type="text">
要素)とほぼ同様に記述することができます。
例えば、テキスト入力用に <textarea>
要素を使用する場合も、次のように onChange
属性で指定したイベントハンドラ内でステートを更新すれば OK です。
const handleChange = ( e : React.ChangeEvent < HTMLTextAreaElement >) => {
setMessage ( e . target . value );
};
// ...
< textarea value = { message } onChange = { handleChange } />
注意点としては、イベントハンドラのパラメータの型が微妙に変わる ということです(型パラメータとして HTMLInputElement
ではなく、HTMLTextAreaElement
を使用します)。
また、通常の HTML の textarea
要素の場合、<textarea>こんにちわ</textarea>
のように、開始タグと終了タグの間に値を記述しましたが、JSX の textarea
の値は value
属性で指定する ことに注意してください(他の input
要素と共通の value
属性で値をセットできるよう考慮されています)。
各種フォーム要素の実装例 FormEvent
型のイベントオブジェクトを受け取ります。
const handleSubmit = ( e : React.FormEvent ) => {
e . preventDefault ();
alert ( 'メッセージ: ' + message );
};
< form onSubmit = { handleSubmit }>
...
< input type = "submit" value = "送信" />
< button type = "submit" onClick = { handleSubmit }> 送信 </ button >
</ form >
input type=“text” 要素 ChangeEvent<HTMLInputElement>
型のイベントオブジェクトを受け取ります。
const handleChange = ( event : React.ChangeEvent < HTMLInputElement >) => {
setMessage ( event . target . value );
}
< input type = "text" value = { message } onChange = { handleChange } />
ChangeEvent<HTMLInputElement>
型のイベントオブジェクトを受け取ります(type=“text” と同じ)。
export const MyForm : React.FC = () => {
const [ isOpen , setIsOpen ] = React . useState ( false );
const handleChange = ( e : React.ChangeEvent < HTMLInputElement >) => {
setIsOpen ( e . target . checked );
}
return < form >
< label > Is open :
< input type = "checkbox" checked = { isOpen } onChange = { handleChange } />
</ label >
</ form >;
};
textarea 要素 ChangeEvent<HTMLTextAreaElement>
型のイベントオブジェクトを受け取ります。
const handleChange = ( e : React.ChangeEvent < HTMLTextAreaElement >) => {
setMessage ( e . target . value );
}
< textarea value = { message } onChange = { handleChange } />
select 要素 ChangeEvent<HTMLSelectElement>
型のイベントオブジェクトを受け取ります。
import * as React from 'react' ;
export const MyForm : React.FC = () => {
const [ color , setColor ] = React . useState ( 'red' );
const handleSubmit = ( e : React.FormEvent ) => {
e . preventDefault ();
alert ( 'Color: ' + color ); //=> 'red'
}
const handleChange = ( e : React.ChangeEvent < HTMLSelectElement >) => {
setColor ( e . target . value );
}
return < form onSubmit = { handleSubmit }>
< label >
Pick your favorite color :
< select value = { color } onChange = { handleChange }>
< option value = "red" > Red </ option >
< option value = "green" > Green </ option >
< option value = "yellow" > Yellow </ option >
</ select >
</ label >
< input type = "submit" value = "Submit" />
</ form >;
};
キーハンドル onKeyDown
や onKeyPress
属性でセットしたイベントハンドラには、KeyboardEvent
オブジェクトが渡されます。
このオブジェクトからどんな値を取得できるかは、公式の Keyboard Events の説明 を参照してください。
import * as React from 'react' ;
export const MyForm : React.FC = () => {
const [ message , setMessage ] = React . useState ( '' );
const handleKeyEvent = ( e : React.KeyboardEvent ) => {
console . log ( e );
}
const handleChange = ( e : React.ChangeEvent < HTMLInputElement >) => {
setMessage ( e . target . value );
}
return < form >
< input type = "text" value = { message }
onKeyDown = { handleKeyEvent } onChange = { handleChange } />
</ form >;
};
フォームに複数の input 要素を配置する場合、各要素の value
属性と onChange
属性をセットするのが煩わしく感じるかもしれません。
そのようなときは、下記の useInput
関数のように、input 要素用の属性値(value
と onChange
)を生成して返す関数を作ると楽になるかもしれません。
components/MyForm.tsx import * as React from 'react' ;
// カスタムフックを定義(input 要素用の属性を生成する)
const useInput = ( initValue ) => {
const [ value , setValue ] = React . useState ( initValue );
return {
value ,
onChange : ( e : React.ChangeEvent < HTMLInputElement >) => setValue ( e . target . value )
};
};
export const MyForm : React.FC = () => {
const msg1 = useInput ( '' );
const msg2 = useInput ( '' );
const handleSubmit = ( e : React.FormEvent ) => {
e . preventDefault ();
alert ( msg1 . value + ', ' + msg2 . value );
};
return < form onSubmit = { handleSubmit }>
< label > Message1 : < input type = "text" { ...msg1 } /></ label >
< label > Message2 : < input type = "text" { ...msg2 } /></ label >
< input type = "submit" value = "送信" />
</ form >;
};
ちなみに、この useInput
関数のように内部でフック関数(useState
など)を呼び出すものを、カスタムフック と呼びます。
カスタムフックにも呼び出し順序などの制約が生まれる、標準のフック関数と同様に use
で始まる名前を付けることが推奨されています。
React アプリでフォームを作るなら React アプリにおけるフォームの扱い方を理解したら、実際には React Hook Form (react-hook-form) のようなライブラリを使ってフォームを作成することをオススメします。
下手に自力でフォームを処理するよりも、簡潔で高速なコードを記述できます。
関連記事