Reactでリアクティブなvalidationフォームの作成

###作りたいもの
Reactだけでデータが入力されたタイミングでバリデーションを実行するフォームの実装。エラーが存在する場合はエラー内容を表示し、ボタンをdisabledにする。
![スクリーンショット 2020-08-15 17.12.32.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/585585/d9b1d6b5-8808-ae42-3013-b83fbd5c52d1.png)

###完成形
```javascript
import React from 'react';
import ReactDOM from 'react-dom';

class Form extends React.Component{
    constructor(props){
        super(props);
        this.state = {
            info: {
                email: '',
                content: ''
            },
            message:{
                email: '',
                content: ''
            }
        };
        this.handleChange = this.handleChange.bind(this);
    }

    handleChange(event) {
        const name = event.target.name;
        const value = event.target.value;
        const { info, message } = this.state;
        this.setState({
            info: { ...info, [name]: value }
        });
        this.setState({
            message: {...message,[name]: this.validator(name, value)}
        });
    }

    validator(name, value){
        switch (name) {
            case 'email':
            return this.emailValidation(value);
            case 'content':
            return this.contentValidation(value);
        }
    }

    emailValidation(value){
        if (!value) return '※メールアドレスを入力してください';
        const regex = /^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/;
        if (!regex.test(value)) return '※正しい形式でメールアドレスを入力してください';
        return '';
    }

    contentValidation(value){
        if (!value) return '※内容を入力してください';
        if (value.length < 8) return '※内容は8文字以上で入力してください';
        return '';
    }

    render(){
        const { info, content } = this.state;
        return (
            <div>
            <p>メールアドレス（必須）</p>
            <p>{this.state.message.email}</p>
            <input type="text" name="email" value={this.state.info.email} onChange={this.handleChange} size="30" />
            <p>お問い合わせ内容（必須）</p>
            <p>{this.state.message.content}</p>
            <textarea name="content" onChange={this.handleChange} defaultValue={this.state.info.content} cols="50" rows="5"></textarea>
            <input type="submit" value="送信する"
            disabled={!this.state.info.email || !this.state.info.content || this.state.message.email || this.state.message.content } />
            </div>
        );
    }
}

export default Form;


if (document.getElementById('app')) {
    ReactDOM.render(
        <Form />,
        document.getElementById('app')
    );
}
```


###解説
inputで入力されたデータを「info」、エラーメッセージを「message」でstate管理します。
bindするのはお忘れなく。

```javascript
constructor(props){
    super(props);
    this.state = {
        info: {
            email: '',
            content: ''
        },
        message:{
            email: '',
            content: ''
        }
    };
    this.handleChange = this.handleChange.bind(this);
}
```

inputから入力されたデータをstateに渡します。このタイミングでvalidatorメソッドを呼び出し、エラーが存在する場合はmessageに格納します。

```javascript
handleChange(event) {
    const name = event.target.name;
    const value = event.target.value;
    const { info, message } = this.state;
    this.setState({
        info: { ...info, [name]: value }
    });
    this.setState({
        message: {...message,[name]: this.validator(name, value)}
    });
}
```

inputのnameによって呼び出す関数を指定します。今回はメールアドレスが正しいメールアドレス形式にされているか正規表現と、お問い合わせ内容が指定した文字以上になっているかをチェックします。

```javascript
validator(name, value){
    switch (name) {
        case 'email':
        return this.emailValidation(value);
        case 'content':
        return this.contentValidation(value);
    }
}

emailValidation(value){
    if (!value) return '※メールアドレスを入力してください';
    const regex = /^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/;
    if (!regex.test(value)) return '※正しい形式でメールアドレスを入力してください';
    return '';
}

contentValidation(value){
    if (!value) return '※内容を入力してください';
    if (value.length < 8) return '※内容は8文字以上で入力してください';
    return '';
}
```

データが入力されていない、もしくはエラーメッセージが存在する場合は、ボタンのdisabledをtrueにします。

```javascript
<input type="submit" value="送信する" 
disabled={!this.state.info.email || !this.state.info.content || this.state.message.email || this.state.message.content } />
```