ReactとHTML5 Form Validationでリアルタイムチェック機能付きフォームを作る

React.js Advent Calendar 2015 – Qiita23日目の記事です。自分はUI周りでのReactの使い方を紹介しようと思います。UIが大事になってくる部分としてフォームの実装が挙げられます。最近ではフォームの実装にReactが使われることも増えてきていますので、実戦で使えそうなリアルタイムValidation機能を伴ったフォームをReactで実装する方法について書いていこうと思います。基本的な部分のみの説明となりますが、フォーム作りの参考にしていただければと思います。

react.js(リアクト)

サンプル

まずはサンプルを見てみてください。

別ウィンドウでサンプルを開く

サンプルについて

  • メールアドレスとURLを入力して送信するフォームです。
  • リアルタイムで入力値のフォーマットと入力の有無のチェックを行います。
  • 入力値に問題がなければ、送信ボタンが押せるようになります。
  • ボタンを押すと、入力値がアラートとして表示されます。

※サンプルなので、かなりシンプルに作っています。

サンプルのソースコードはGitHubで確認可能です。

実際にReactの実装を行っているのが以下のJSファイルとなります。

実装方法の説明の前に

Reactの基礎知識の説明や開発環境の構築方法などは省略します。その辺の情報については、当ブログの以下の記事などを参考にしてください。

また、コードはES2015(ES6)の構文を一部取り入れて書いています。ただしコンポーネントを書くときはclass構文は使わず、従来通りオプジェクトリテラル形式で書いています。完全なES2015で書きたい場合は以下などを参考にしてください。

コンポーネントの構成

それでは早速説明に入ります。まず、コンポーネントの構成は以下のようになります。親コンポーネントとなるFormAppに、フォームの各パーツが子コンポーネントとしてぶら下がった状態になっています。

- FormApp - FormMail(メールアドレス入力フォーム) - FormUrl(URL入力フォーム) - FormButton(送信ボタン)

親となるFormAppコンポーネントの実装

まず最初に親コンポーネントとなるFormAppコンポーネントを作ります。とりあえず大枠だけ作ると以下のようになります。

const FormApp = React.createClass({
  getInitialState() {
    return {}
  },
  render() {
    return (
      <ul>
        <FormMail />
        <FormUrl />
        <FormButton />
      </ul>
    );
  }
});
FormAppコンポーネント(親)

次にこのサンプルを通して扱う値を定義します。今回のフォームでは「メールアドレス」「URL」のみを扱います。getInitialState()メソッドの戻り値に以下のようなオブジェクトをセットします。定義した値の詳細はコメントを見てください。

const FormApp = React.createClass({
  getInitialState() {
    return {
      // フォームに入力される値 {string}
      data: {
        mail: null,
        url: null
      },
      // バリデーションエラー時のメッセージ {string}
      message: {
        mail: null,
        url: null
      },
      // フォームのバリデーションの状態 {boolean}
      status: {
        mail: null,
        url: null
      }
    };
  },
  render() {
    return (
      <ul>
        <FormMail />
        <FormUrl />
        <FormButton />
      </ul>
    );
  }
});
FormAppコンポーネント(親)

扱う値を定義したら、子コンポーネントに値を渡せるようにrender()メソッド内のJSXを以下のように修正します。this.statestateの値を取得して渡すようにします。

const FormApp = React.createClass({
  getInitialState() {
  return {
      // フォームに入力される値 {string}
      data: {
        mail: null,
        url: null
      },
      // バリデーションエラー時のメッセージ {string}
      message: {
        mail: null,
        url: null
      },
      // フォームのバリデーションの状態 {boolean}
      status: {
        mail: null,
        url: null
      }
    };
  },
  render() {
    let mail = {
      mail: this.state.data.mail,
      error: this.state.message.mail,
      checkValue: this.checkValue
    };
    let url = {
      url: this.state.data.url,
      error: this.state.message.url,
      checkValue: this.checkValue
    };
    let button = {
      mail: this.state.status.mail,
      url: this.state.status.url,
      sendData: this.sendData
    };
    return (
      <ul>
        <FormMail {...mail} />
        <FormUrl {...url} />
        <FormButton {...button} />
      </ul>
    );
  }
});
FormAppコンポーネント(親)

上記の{…mail}という書き方は、JSXのSpread Attributesという書き方です。詳細は以下をご参照ください。

親(ルート)コンポーネントは、また後でいじることになりますが、まずはこんな感じにしておきます。

子コンポーネント(入力フォーム部分)の実装

次に、子コンポーネントを作ります。子コンポーネントは、実際に「値を入力する部分であるフォーム」と「ボタン」の2タイプ作ることになりますが、まずは入力フォーム部分から作っていきます。

フォーム部分は、入力フォーム1つにつき1つのコンポーネントを割り当てることにします。ここではFormMailFormUrlの2つのコンポーネントを作ります。この2つのコンポーネントはほぼ同じ内容になるので、とりあえずFormMailコンポーネントの作り方を説明していきます。

Reactのコンポーネントで扱うFormの仕様については以下をご参照ください。

FormMailコンポーネントの作成

まずFormMailコンポーネントの大枠は以下のようになります。propsを使って、親コンポーネントから渡される値を取得するようにしています。取得した値は、「formのvalueの値」「エラーメッセージ出力用の値」として使います。

const FormMail = React.createClass({
  render() {
    return (
      <li>
        <input type="email" name="mail" placeholder="Email"
          value={this.props.mail}
        />
        <p>{this.props.error}</p>
      </li>
    );
  }
});
FormMailコンポーネント(子)

フォーム内にイベントリスナーをセット

大枠ができたら、早速処理を入れていきます。今回はリアルタイムに入力値をチェックするようにしたいので、「フォームに値が入力された時(onCange)」と「フォームからーソルが外れた時(onBlur)」にイベントを発火するようにします。それから入力を必須(required)にします。JSXのinput要素にonChenge属性、onBlur属性、required属性を追加しますonChenge属性、onBlur属性の値には、コールバック関数としてthis._checkValueをバインドします。

const FormMail = React.createClass({
  render() {
    return (
      <li>
        <input type="email" name="mail" placeholder="Email"
          value={this.props.mail}
          onChange={this._checkValue}
          onBlur={this._checkValue}
          required
        />
        <p>{this.props.error}</p>
      </li>
    );
  }
});
FormMailコンポーネント(子)

イベントにバインドしたコールバック関数の定義

バインドしたコールバック関数の処理を定義します。このコールバック関数は、FormMailFormUrlの2つのコンポーネントで共通の処理として使うので、mixin用にCheckValueというオブジェクトにして外出しします。CheckValueオブジェクト内で、_checkValueというコールバック関数を定義し、「フォームのname属性の値」と「入力された値」を取得して、propsで受け取ったcheckValue()メソッドの引数にセットするようにします

// 共通処理をCheckValueオブジェクトとして定義
const CheckValue = {
  // コールバック関数を定義
  _checkValue(event) {
    // フォームのname属性値を取得
    let type = event.target.name;
    // フォームの入力値を取得
    let val = event.target.value;
    // 親コンポーネントから受け取るcheckValue()メソッドを実行
    this.props.checkValue(type, val, event);
  }
};

const FormMail = React.createClass({
// 共通処理として定義したCheckValueオブジェクトを読み込む
  mixins: [CheckValue],
  render() {
    return (
      <li>
        <input type="email" name="mail" placeholder="Email"
          value={this.props.mail}
          onChange={this._checkValue}
          onBlur={this._checkValue}
          required
        />
        <p>{this.props.error}</p>
      </li>
    );
  }
});
FormMailコンポーネント(子)

mixinについては以下をご参照ください。

checkValue()メソッドを定義

FormMailFormUrlの子コンポーネントに値が入力された際に、最終的に呼び出されるのが、checkValue()メソッドとなります。このcheckValue()メソッドは、親コンポーネントであるFormApp内で定義します。ポイントは以下の通りです。

  • フォームの「name属性値」「入力値」「イベントobject」を引数に取ります。
  • データを一時的に保持するdatamessagestatusの3つの変数を定義し、初期値としてstateの値をセットします。
  • 引数に渡されたname属性値で処理を分岐(Switch文)します。
  • Switch文のそれぞれの処理において一時保存用変数に値をセットします。
  • 最終的にthis.setState()メソッドにdatamessagestatusの値を渡して、stateを更新します

コードは以下のようになります。(ここでのswitch文における処理内容は、入力値に問題がなかった場合の処理となります。後ほどエラー時の処理を追加していきます。)

const FormApp = React.createClass({

 /* 省略 */

  checkValue(type, value, event) {
    // データを一時的に保持するための変数
    let data = {
      mail: this.state.data.mail,
      url: this.state.data.url
    };
    let message = {
      mail: this.state.message.mail,
      url: this.state.message.url
    };
    let status = {
      mail: this.state.status.mail,
      url: this.state.status.url
    };

    // name属性値によって処理を分岐
    switch(type) {
      // name属性値がmailだった場合
      case "mail":
        // 一時保存用変数に値をセット
        data.mail = value;
        message.mail = null;
        status.mail = true;
        break;
      // name属性値がurlだった場合
      case "url":
        // 一時保存用変数に値をセット
        data.url = value;
        message.url = null;
        status.url = true;
        break;
    }
    // stateを更新
    this.setState({
      data: data,
      message: message,
      status: status
    });
  },
  
 /* 省略 */

});
FormAppコンポーネント(親)

続いて、このメソッドにバリデージョンの機能を追加していきます

HTML5のForm Validationで入力チェック機能を実装

バリデーション機能は様々な方法で実装が可能ですが、今回はシンプルに説明するためにHTML5の「Form Validation」を使用することにします。

HTML5のForm Validationについては以下をご参照ください。

ここでの実装のポイントは以下の通りです。難しいことは全然やっていません。

  • input要素をevent.targetで取得
  • input要素の「type属性に指定した値」と「入力した値」が一致しているかFormが自動でチェックしてくれる
  • required属性を指定しているので、同じくFormが自動で入力の有無のチェックしてくれる
  • 入力値にエラーがある場合は、event.target.validationMessageに自動でエラーメッセージがセットされる
  • event.target.validationMessageにメッセージが存在する場合は、statemessageevent.target.validationMessageの値をセットし、statestatusの値をfalseにする

親コンポーネントで定義したcheckValue()メソッド内のswitch文内の処理を以下のようにします。入力値にエラーがある場合とない場合で処理をわけます

switch(type) {
  case "mail":
    data.mail = value;
    if (event.target.validationMessage) {
      message.mail = event.target.validationMessage;
      status.mail = false;
    } else {
      message.mail = null;
      status.mail = true;
    }
    break;
FormAppコンポーネント(親)のcheckValue()メソッド内のSwitch文

Validationの機能はこんな感じで実装することができます。HTML5のForm Validationはかなり機能が充実しています。文字数のチェックもできますし、自分でチェックしたいパターンを指定することも可能です。エラーメッセージを勝手に吐き出してくれるのも実装にあまり時間がない時などは大変重宝することと思います。

ちなみにForm Validationの各ブラウザの実装状況は以下の通りです。ほぼほぼOKな感じです。

ValidationのStyleを実装

入力値にエラーがある場合は、「エラーメッセージを出力したり」、「エラーの箇所を知らせたり」する必要があります。ここで大事なことは、入力者に気づいてもらうためにそれらを強調させることです。エラー時にフォームのスタイルを変更する方法を紹介します。

CSSの擬似クラス:valid、:invalid

バリデーションのスタイルの実装にはCSSの擬似クラスである:valid:invalidを使います。

今回のサンプルでは、エラーがあった場合は、フォームの枠の色を赤系の色に変更しています。上記で定義した子コンポーネントのJSX内に以下のようにstyle属性を追加しますstyle属性にセットする値はmixinで読み込ませるためにValidStyleオプジェクトとして外出ししています。this.props.errorには、上記であまり詳しく説明していませんでしたが、statemessageの値が渡されるようになっていて、messageの値がある場合(エラーの場合)は、this.style.invalidの値がmessageの値がない場合(エラーがない場合)は、this.styel.validの値がセットされるようになっています。

const ValidStyle = {
  style: {
    // エラーがある場合のスタイル(枠の色赤系)
    invalid: {
      border: "2px solid #b71c1c"
    },
    // エラーがない場合のスタイル(枠の色グレー系)
    valid: {
      border: "2px solid #ccc"
    }
  }
};

const FormMail = React.createClass({
// 共通処理であるValidStyleを読み込む
  mixins: [CheckValue, ValidStyle],
  render() {
    return (
      <li>
        <input type="email" name="mail" placeholder="Email"
          value={this.props.mail}
          onChange={this._checkValue}
          onBlur={this._checkValue}
          required
          style={(this.props.error) ? this.style.invalid : this.style.valid}
        />
        <p>{this.props.error}</p>
      </li>
    );
  }
});
FormMailコンポーネント(子)

エラーメッセージはJSX内のp要素内に表示されるようになっています。エラーメッセージのスタイルを設定する場合は、このp要素に対してスタイルを設定してあげます

子コンポーネント(送信ボタン部分)の実装

最後に送信ボタンの実装です。送信ボタンは入力した値にエラーがなかった場合にのみ押せるようにします。

FormButtonコンポーネントの作成

ボタン部分を形成するFormButtonコンポーネントを作ります。まず大枠は以下の通りです。

const FormButton = React.createClass({
  render() {
    return (
      <li>
        <button onClick={this._sendData}>
          送信する
        </button>
      </li>
    );
  }
});
FormButtonコンポーネント(子)

Button要素にdisabled属性を追加し「押す/押せない」機能を実装

入力値の状態でボタンを「押す/押せない」ようにするには、JSX内のbutton要素にdisabled属性を追加して実装しますdisabled属性の値になっているthis.props.mailthis.props.urlには、上記であまり詳しく説明していませんでしたが、statestatusの値がそれぞれ渡されるようになっています。どちらかのstatusの値が「false」であれば、disabled属性は「ture」となりボタンは押せない状態となります。どちらのstatusの値も「true」になることで、ボタンは押せるようになります

const FormButton = React.createClass({
  render() {
    return (
      <li>
        <button disabled={this.props.mail === false || this.props.url === false}>
          送信する
        </button>
      </li>
    );
  }
});
FormButtonコンポーネント(子)

ボタンを押した時の処理を実装

最後にボタンを押した時の処理を実装します。JSX内のbutton要素にonClick属性を追加して、コールバック関数としてthis._sendDataをバインドします_sendDataの処理もFormButtonコンポーネント内に定義し、親コンポーネントから渡されるthis.props.sendData()を実行するようにします。

const FormButton = React.createClass({
  _sendData(event) {
    event.preventDefault();
    this.props.sendData();
  },
  render() {
    return (
      <li>
        <button onClick={this._sendData}
          disabled={this.props.mail === false || this.props.url === false}
        >
          送信する
        </button>
      </li>
    );
  }
});
FormButtonコンポーネント(子)

sendData()メソッドを定義

sendData()メソッドは親コンポーネントで定義します。今回のサンプルでは、alert()メソッドを使って入力された値を表示するだけにしていますが、ここでajaxを使ったりしてサーバーとの通信を行ったりすると良いでしょう

const FormApp = React.createClass({

 /* 省略 */

  sendData() {
    alert(this.state.data.mail + ", " + this.state.data.url);
  },
  
 /* 省略 */

});
FormAppコンポーネント(親)

完成

Reactの実装は以上です。最後にReactDOMモジュール(React v0.14以上の場合)のrender()メソッドで、HTMLにレンダリングするようにします。ファイル名を「app.js」とでもして、BabelBrowserifyなどを使ってJSXとES2015(ES6)のコンパイルをしてあげるとHTMLファイル側で読み込めるようになります

ReactDOM.render(
  <FormApp />,
  document.getElementById("content")
);
app.js

コンパイル後のファイル(bundle.js)をHTMLファイルに読み込みます。

<body>
  <h1>React Validation Form</h1>
  <div id="content"></div>
  <script src="bundle.js"></script>
</body>
index.html

長くなりましたが、以上でReactで作るValidation Formのサンプルは完成です。Reactでの実装コードについては、GitHubにアップしているのでそちらでご確認ください。

Fluxで実装

flux-logo
Flux

Reactだけで実装すると、親コンポーネント内にいろいろと処理を詰め込みすぎて、データの流れなども見えにくくなるので、Fluxで実装することもオススメです。上記の実装をFluxで書いたものもありますので、そちらも合わせてご確認ください。

Fluxの実装については、Reduxというフレームワークが一つ飛び抜けた感がありますが、まだ自分はそこまでは抑えていません。自分の場合はFacebook製のFluxで用意されている「dispatcher.js」を使って実装しています。dispatcher.jsは結構シンプルで理解もしやすくなっているので、Fluxの実装もしやすいと思います。

当ブログでは「dispatcher.js」についての記事も書いていますので、参考にしてみてください。

まとめ

Reactの記事として書いてきましたが、一番肝となるのは途中で軽く紹介したHTML5のForm Validation機能だったりするんじゃないかと思っています。自分もHTML5のForm Validation機能について深く理解しているわけではないので、もっと理解が進めば、さらにやれることも広がってくるんじゃないかと思っています。

それからReactでのフォーム作りは、AngularJSの手軽さと比べると、書くコードの量も多くて時間もそれなりにかかってしまうことと思います。ただそうであってもReactの場合は全体を通してシンプルに流れるように書けるようになっています。その辺りがReactの面白さであり魅力かなと勝手に思っています。ぜひ、当記事を参考にReactでのフォーム作りにトライしていただけたら幸いです。

入門 React ―コンポーネントベースのWebフロントエンド開発
  • 『入門 React ―コンポーネントベースのWebフロントエンド開発』
  • 著者: Frankie Bagnardi, Jonathan Beebe, Richard Feldman, Tom Hallett, Simon HØjberg, Karl Mikkelsen, 宮崎 空(翻訳)
  • 出版社: オライリージャパン
  • 発売日: 2015年4月3日
  • ISBN: 978-4873117195

コメント一覧

  • 必須

コメント