質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

90.03%

(React.js) Sign up機能 with real time validationにてstateが更新されない

解決済

回答 1

投稿 編集

  • 評価
  • クリップ 0
  • VIEW 356

kazoogon

score 269

今していること

react.jsを使用し、リアルタイムバリデーション機能の付いたユーザー登録画面の作成

質問

setStateでstateを更新しようとしてもうまくいかない
(inputで入力された値を更新しようとしています)

該当部分は下記コード内にて記載いたしました。

コード

(関係ないと思われる部分は省いています)

import React, { Component } from 'react';


class Register extends Component {
  constructor(props) {
    super(props);
    this.state = {
      firstName: {
        value: '',
        status: ''
      },
      lastName: {
        value: '',
        status: ''
      },
      email: {
        value: '',
        status: ''
      },
      password: {
        value: '',
        status: ''
      },
      password2: {
        value: '',
        status: ''
      }
    };
  }

  tmpClassName = tmp => {
    const prefix = 'form-control ';

    switch (tmp) {
      case '':    return prefix + '';
      case true:  return prefix + 'is-valid';
      case false: return prefix + 'is-invalid';
    }
  };

  onChange = e => {
    const currentName = e.target.name;
    const currentValue = e.target.value;

    //ここで、valueが更新されない、というか消える
    this.setState({
      [currentName]: {
        value: currentValue
      }
    });

    switch(currentName){
      case 'firstName':
        if(currentValue !== '' && currentValue.length >= 2) {
          this.setState({
            firstName: {
              status: true
            }
          })
        }else{
          this.setState({
            firstName: {
              status: false
            }
          })
        }
      break;

      case 'lastName':
        if(currentValue !== '' && currentValue.length >= 2) {
          this.setState({
            lastName: {
              status: true
            }
          })
        }else{
          this.setState({
            lastName: {
              status: false
            }
          })
        }
      break;

      case 'email':
        const invalidEmail = currentValue.match(/^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/);
        if(invalidEmail && currentValue !== ''){
          this.setState({
            email: {
              status: true
            }
          })
        }else{
          this.setState({
            email: {
              status: false
            }
          })
        }
      break;

      case 'password':
        if(currentValue.length >= 6){
          this.setState({
            password: {
              status: true
            }
          })
        }else{
          this.setState({
            password: {
              status: false
            }
          })
        }
      break;

      case 'password2':
        if(currentValue.length >= 6 && currentValue == this.state.password.value){
          this.setState({
            password2: {
              status: true
            }
          })
        }else{
          this.setState({
            password2: {
              status: false
            }
          })
        }
      break;
    }
  };

  onSubmit = e => {
    e.preventDefault();

  }

  render() {
    return (
      <div className="row register offset-md-2 col-md-8 col-xs-12">
        <form onSubmit={ this.onSubmit }>
          <fieldset>

            <h1>Sign Up</h1>

            <div className="form-group name">
              <input
                type="firstName"
                className={this.tmpClassName(this.state.firstName.status)}
                name="firstName"
                placeholder="First Name"
                value={this.state.firstName.value}
                onChange={this.onChange}
                minLength="2"
                required
              />
              <input
                type="lastName"
                className={this.tmpClassName(this.state.lastName.status)}
                name="lastName"
                placeholder="Last Name"
                value={this.state.lastName.value}
                onChange={this.onChange}
                minLength="2"
                required
              />
            </div>

            <div className="form-group">
              <input
                type="email"
                className={this.tmpClassName(this.state.email.status)}
                name="email"
                aria-describedby="emailHelp"
                placeholder="Email"
                value={this.state.email.value}
                onChange={this.onChange}
                required
              />
              <small id="emailHelp" className="form-text text-muted">
                We'll never share your email with anyone else.
              </small>
            </div>

            <div className="form-group">
              <input
                type="password"
                className={this.tmpClassName(this.state.password.status)}
                name="password"
                placeholder="Password"
                value={this.state.password.value}
                onChange={this.onChange}
                minLength="6"
                maxLength="100"
                required
              />
            </div>

            <div className="form-group">
              <input
                type="password"
                className={this.tmpClassName(this.state.password2.status)}
                name="password2"
                placeholder="Confirm Password"
                value={this.state.password2.value}
                onChange={this.onChange}
                minLength="6"
                maxLength="100"
                required
              />
            </div>

            <button
              type="submit"
              /*statusのlength取って、classを加えていく感じやな*/
              className={`btn btn-primary
                ${this.state.firstName.status? 'quarter' : ''} +
                ${this.state.lastName.status? 'half' : ''} +
                ${this.state.email.status? 'three-quarters' : ''}`
              }
              disabled
            >
            Submit
            </button>

          </fieldset>
        </form>
      </div>
    );
  }
}

export default Register;

よろしくお願いいたします。

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

質問への追記・修正、ベストアンサー選択の依頼

  • jun68ykt

    2018/10/15 07:49

    こんにちは。回答に書いたとおり「コード全体を見ないとはっきりしない」ところがあります。特に、(1) tmpClassName はどのようなことをしているのか?(2) status によって切り替わる各 input のとりえるクラス名とそれらによって当たるスタイル。これら2点が不明だとソースコードの中にコメントで書いてある、 "(というか消える)"  がどういうことか回答者各位に伝わりにくいと思われます。

    キャンセル

  • kazoogon

    2018/10/15 12:08

    コード追記いたしました、よろしくお願いいたします。

    キャンセル

  • jun68ykt

    2018/10/15 13:39

    追記ありがとうございます。tmpClassName の問題について回答のほうのコメントに書きました。

    キャンセル

回答 1

checkベストアンサー

0

こんにちは。

コード全体を見ないとはっきりしないのですが、ぱっと見で思ったのは、ご質問のコードだと、this.setStateするときに当該currentNameの statusプロパティが失われてしまうので、そのために意図した動作になっていないのでは? ということです。

ですので、this.setStateしているところを、以下のようにしてみるといかがでしょうか?

修正前:

this.setState({
      [currentName]: {
        value: currentValue
      }
    });

修正後:

this.setState({
      [currentName]: {
        value: currentValue,
        status: this.state[currentName].status
      }
    });

上記の修正では this.setStateしたときに当該currentNameの statusプロパティは変わらないようにしています。

以上参考になれば幸いです。

 追記

kazoogon 2018/10/15 12:10
回答ありがとうございます。
これは例えばプロパティが10個あったら、setStateする度に10個書かないといけないということ?? になるんですかね??

上記に回答します。

プロパティが10個あったら

というのは、this.state の各currentName の値であるオブジェクトに10個のプロパティがあり、たとえば

this.state = {
   firstName: {
        value: '',
        status: '',
        prop1: '', prop2: '', prop3: '', prop4: '',
        prop5: '', prop6: '', prop7: '', prop8: '',
   } 
}

というものだった場合、setStateするときに、1個のプロパティは変更するとして、残りの変更したくない9個のプロパティについて、すべて先の回答の status: this.state[currentName].status のようなプロパティのコピーを個別に書かなければならないか? というご質問だと思います。(違っていたら、またコメント頂ければと思います)

回答としては、その必要はなく、...によるスプレッドを使って以下のように書けばよいかと思います。

this.setState({
      firstName: {
        ...this.state.firstName,
        value: currentValue,
      }
    });

上記により、 first の内容としては、valueだけが更新されており他は変更なしのオブジェクトで setStateされます。

 追記2

ご質問に挙げられているコードから始めて、意図通り動くようにし、入力値のバリデーションを関数化したものを以下のレポジトリ

に上げましたので、もしよければ git cloneして頂くか、ダウンロードのうえ、内容をご確認ください。
以下、このレポジトリに入れたソースについて説明します。

(1) 最初のコミットでは、ご質問に挙げられている Register をそのままコピペして Register.js を作成し、これだけを表示する index.js を作成しました。

(2) 次のコミット、「一度のsetStateでvalueとstateを更新するように修正」にて、 onChange の中で一度だけsetStateするようにしています。これで、とりあえず意図通り動くようになるかと思います。

(3) 入力値のバリデーションを関数化 することで、onChange を以下のように短くしました。

  onChange = e => {
    const { name, value } = e.target;

    let status = validate(name, value);
    if (name === 'password2')
      status = status && this.state.password.value === value;

    this.setState( { [name]: { value, status } });
  };

以上です。

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/10/15 21:59

    ご質問に挙げられているコードから始めて、意図通り動くようにし、入力値のバリデーションを関数化したものを作りました。回答のほうに追記2を書きましたのでご覧ください。

    キャンセル

  • 2018/10/16 02:48

    今回もありがとうございました。
    まさかここまでコードが短くなるとは驚きです、どんな教科書よりも役に立つ物を与えてくださり感謝の気持ちでいっぱいです

    キャンセル

  • 2018/10/16 06:38

    どういたしまして。お役に立てたようでよかったです 👍

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 90.03%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる