シンプルなフォームがあります。すべてのコンポーネントと状態は、ページコンポーネントに保持されます。 2つの表示ヘッダーと3つの入力フィールドがあります。最初の入力はテキストであり、2番目と3番目はintであると想定されています。ユーザーが間違ったタイプのデータを入力した場合、入力フィールドの横にエラーメッセージがポップアップ表示されるようにします。私の質問はReact.JSのベストプラクティスに関連しています
値が有効であると誰が判断しますか?入力フィールドの唯一の仕事は、状態を保持するコンポーネントに値を戻すことだと思うので、これは、Pageのみが値が有効かどうかを判断できるということですか?
ポップアップを表示するにはどうすればよいですか? Pageは、perpを介してエラーメッセージを表示するようにAdaptive_Inputに渡す新しいブール状態要素をトリガーする必要がありますか?
JS:
/**
* @jsx React.DOM
*/
var Adaptive_Input = React.createClass({
handle_change: function(){
var new_text = this.refs.input.getDOMNode().value;
this.props.on_Input_Change(new_text);
},
render: function(){
return (
<div className='adaptive_placeholder_input_container'>
<input
className="adaptive_input"
type="text"
required="required"
onChange= {this.handle_change}
ref="input"
></input>
<label
className="adaptive_placeholder"
alt={this.props.initial}
placeholder={this.props.focused}
></label>
</div>
);
}
});
var Form = React.createClass({
render: function(){
return (
<form>
<Adaptive_Input
initial={'Name Input'}
focused={'Name Input'}
on_Input_Change={this.props.handle_text_input}
/>
<Adaptive_Input
initial={'Value 1'}
focused={'Value 1'}
on_Input_Change={this.props.handle_value_1_input}
/>
<Adaptive_Input
initial={'Value 2'}
focused={'Value 2'}
on_Input_Change={this.props.handle_value_2_input}
/>
</form>
);
}
});
var Page = React.createClass({
getInitialState: function(){
return {
Name : "No Name",
Value_1 : '0',
Value_2 : '0',
Display_Value: '0'
};
},
handle_text_input: function(new_text){
this.setState({
Name: new_text
});
},
handle_value_1_input: function(new_value){
console.log("===");
var updated_display = parseInt(new_value) + parseInt(this.state.Value_2);
updated_display = updated_display.toString();
this.setState({
Display_Value: updated_display
});
},
handle_value_2_input: function(new_value){
var updated_display = parseInt(this.state.Value_1) + parseInt(new_value);
updated_display = updated_display.toString();
this.setState({
Display_Value: updated_display
});
},
render: function(){
return(
<div>
<h2>{this.state.Name}</h2>
<h2>Value 1 + Value 2 = {this.state.Display_Value}</h2>
<Form
handle_text_input={this.handle_text_input}
handle_value_1_input = {this.handle_value_1_input}
handle_value_2_input = {this.handle_value_2_input}
/>
</div>
);
}
});
React.renderComponent(<Page />, document.body);
まず、これから説明する例です。 http://jsbin.com/rixido/2/edit
React.JSで入力値を適切に検証する方法は?
しかし、あなたが望む。 Reactは、データモデルのレンダリング用です。データモデルは、何が有効であるかを知っている必要があります。 Backboneモデル、JSONデータ、またはデータとそのエラー状態を表現する任意のものを使用できます。
すなわち:
通常、Reactはデータに対して非依存です。イベントのレンダリングと処理を目的としています。
従うべき規則は次のとおりです。
何かが小道具か状態かを決定する方法は?これを考慮してください。テキストフィールド以外のアプリのどの部分でも、入力された値が間違っていることを知りたいでしょうか?いいえの場合、状態にします。はいの場合、それは小道具でなければなりません。
たとえば、「このページに2つのエラーがあります」と表示する別のビューが必要な場合。エラーはトップレベルのデータモデルに認識される必要があります。
そのエラーはどこに存在しますか?
アプリがBackboneモデルをレンダリングしている場合(たとえば)、モデル自体にはvalidate()メソッドとvalidateErrorプロパティがあります。同じことができる他のスマートオブジェクトをレンダリングできます。 Reactは、小道具を最小限に抑え、残りのデータを生成しようとすることも示しています。そのため、バリデーターがある場合(例 https://github.com/flatiron/revalidator )、検証がトリクルダウンし、コンポーネントが検証に一致する小道具をチェックして、有効かどうかを確認できます。
それは主にあなた次第です。
(私はpersonallyです。バックボーンモデルを使用し、Reactでそれらをレンダリングします。 )
npm install --save redux-form
を使用できます
メールを検証してフォームを送信する簡単なメールと送信ボタンのフォームを書いています。 redux-formを使用すると、フォームはデフォルトでhtml onSubmitアクションでevent.preventDefault()を実行します。
import React, {Component} from 'react';
import {reduxForm} from 'redux-form';
class LoginForm extends Component {
onSubmit(props) {
//do your submit stuff
}
render() {
const {fields: {email}, handleSubmit} = this.props;
return (
<form onSubmit={handleSubmit(this.onSubmit.bind(this))}>
<input type="text" placeholder="Email"
className={`form-control ${email.touched && email.invalid ? 'has-error' : '' }`}
{...email}
/>
<span className="text-help">
{email.touched ? email.error : ''}
</span>
<input type="submit"/>
</form>
);
}
}
function validation(values) {
const errors = {};
const emailPattern = /(.+)@(.+){2,}\.(.+){2,}/;
if (!emailPattern.test(values.email)) {
errors.email = 'Enter a valid email';
}
return errors;
}
LoginForm = reduxForm({
form: 'LoginForm',
fields: ['email'],
validate: validation
}, null, null)(LoginForm);
export default LoginForm;
Jsfiddleはもう機能しません。修正しました: http://jsfiddle.net/tkrotoff/bgC6E/40/ React 16およびES6クラスを使用します。
class Adaptive_Input extends React.Component {
handle_change(e) {
var new_text = e.currentTarget.value;
this.props.on_Input_Change(new_text);
}
render() {
return (
<div className="adaptive_placeholder_input_container">
<input
className="adaptive_input"
type="text"
required="required"
onChange={this.handle_change.bind(this)} />
<label
className="adaptive_placeholder"
alt={this.props.initial}
placeholder={this.props.focused} />
</div>
);
}
}
class Form extends React.Component {
render() {
return (
<form>
<Adaptive_Input
initial={'Name Input'}
focused={'Name Input'}
on_Input_Change={this.props.handle_text_input} />
<Adaptive_Input
initial={'Value 1'}
focused={'Value 1'}
on_Input_Change={this.props.handle_value_1_input} />
<Adaptive_Input
initial={'Value 2'}
focused={'Value 2'}
on_Input_Change={this.props.handle_value_2_input} />
</form>
);
}
}
class Page extends React.Component {
constructor(props) {
super(props);
this.state = {
Name: 'No Name',
Value_1: '0',
Value_2: '0',
Display_Value: '0'
};
}
handle_text_input(new_text) {
this.setState({
Name: new_text
});
}
handle_value_1_input(new_value) {
new_value = parseInt(new_value);
var updated_display = new_value + parseInt(this.state.Value_2);
updated_display = updated_display.toString();
this.setState({
Value_1: new_value,
Display_Value: updated_display
});
}
handle_value_2_input(new_value) {
new_value = parseInt(new_value);
var updated_display = parseInt(this.state.Value_1) + new_value;
updated_display = updated_display.toString();
this.setState({
Value_2: new_value,
Display_Value: updated_display
});
}
render() {
return(
<div>
<h2>{this.state.Name}</h2>
<h2>Value 1 + Value 2 = {this.state.Display_Value}</h2>
<Form
handle_text_input={this.handle_text_input.bind(this)}
handle_value_1_input={this.handle_value_1_input.bind(this)}
handle_value_2_input={this.handle_value_2_input.bind(this)}
/>
</div>
);
}
}
ReactDOM.render(<Page />, document.getElementById('app'));
そして今、このライブラリのおかげで同じコードがフォーム検証でハッキングされました: https://github.com/tkrotoff/react-form-with-constraints => http:// jsfiddle。 net/tkrotoff/k4qa4heg /
const { FormWithConstraints, FieldFeedbacks, FieldFeedback } = ReactFormWithConstraints;
class Adaptive_Input extends React.Component {
static contextTypes = {
form: PropTypes.object.isRequired
};
constructor(props) {
super(props);
this.state = {
field: undefined
};
this.fieldWillValidate = this.fieldWillValidate.bind(this);
this.fieldDidValidate = this.fieldDidValidate.bind(this);
}
componentWillMount() {
this.context.form.addFieldWillValidateEventListener(this.fieldWillValidate);
this.context.form.addFieldDidValidateEventListener(this.fieldDidValidate);
}
componentWillUnmount() {
this.context.form.removeFieldWillValidateEventListener(this.fieldWillValidate);
this.context.form.removeFieldDidValidateEventListener(this.fieldDidValidate);
}
fieldWillValidate(fieldName) {
if (fieldName === this.props.name) this.setState({field: undefined});
}
fieldDidValidate(field) {
if (field.name === this.props.name) this.setState({field});
}
handle_change(e) {
var new_text = e.currentTarget.value;
this.props.on_Input_Change(e, new_text);
}
render() {
const { field } = this.state;
let className = 'adaptive_placeholder_input_container';
if (field !== undefined) {
if (field.hasErrors()) className += ' error';
if (field.hasWarnings()) className += ' warning';
}
return (
<div className={className}>
<input
type={this.props.type}
name={this.props.name}
className="adaptive_input"
required
onChange={this.handle_change.bind(this)} />
<label
className="adaptive_placeholder"
alt={this.props.initial}
placeholder={this.props.focused} />
</div>
);
}
}
class Form extends React.Component {
constructor(props) {
super(props);
this.state = {
Name: 'No Name',
Value_1: '0',
Value_2: '0',
Display_Value: '0'
};
}
handle_text_input(e, new_text) {
this.form.validateFields(e.currentTarget);
this.setState({
Name: new_text
});
}
handle_value_1_input(e, new_value) {
this.form.validateFields(e.currentTarget);
if (this.form.isValid()) {
new_value = parseInt(new_value);
var updated_display = new_value + parseInt(this.state.Value_2);
updated_display = updated_display.toString();
this.setState({
Value_1: new_value,
Display_Value: updated_display
});
}
else {
this.setState({
Display_Value: 'Error'
});
}
}
handle_value_2_input(e, new_value) {
this.form.validateFields(e.currentTarget);
if (this.form.isValid()) {
new_value = parseInt(new_value);
var updated_display = parseInt(this.state.Value_1) + new_value;
updated_display = updated_display.toString();
this.setState({
Value_2: new_value,
Display_Value: updated_display
});
}
else {
this.setState({
Display_Value: 'Error'
});
}
}
render() {
return(
<div>
<h2>Name: {this.state.Name}</h2>
<h2>Value 1 + Value 2 = {this.state.Display_Value}</h2>
<FormWithConstraints ref={form => this.form = form} noValidate>
<Adaptive_Input
type="text"
name="name_input"
initial={'Name Input'}
focused={'Name Input'}
on_Input_Change={this.handle_text_input.bind(this)} />
<FieldFeedbacks for="name_input">
<FieldFeedback when="*" error />
<FieldFeedback when={value => !/^\w+$/.test(value)} warning>Should only contain alphanumeric characters</FieldFeedback>
</FieldFeedbacks>
<Adaptive_Input
type="number"
name="value_1_input"
initial={'Value 1'}
focused={'Value 1'}
on_Input_Change={this.handle_value_1_input.bind(this)} />
<FieldFeedbacks for="value_1_input">
<FieldFeedback when="*" />
</FieldFeedbacks>
<Adaptive_Input
type="number"
name="value_2_input"
initial={'Value 2'}
focused={'Value 2'}
on_Input_Change={this.handle_value_2_input.bind(this)} />
<FieldFeedbacks for="value_2_input">
<FieldFeedback when="*" />
</FieldFeedbacks>
</FormWithConstraints>
</div>
);
}
}
ReactDOM.render(<Form />, document.getElementById('app'));
ここで提案されている解決策は、元のjsfiddleの近くに維持しようとしたため、ハック的です。 react-form-with-constraintsを使用した適切なフォーム検証については、 https://github.com/tkrotoff/react-form-with-constraints#examples を確認してください
このライブラリ を作成しました。これにより、フォーム要素コンポーネントをラップし、バリデーターを次の形式で定義できます。
<Validation group="myGroup1"
validators={[
{
validator: (val) => !validator.isEmpty(val),
errorMessage: "Cannot be left empty"
},...
}]}>
<TextField value={this.state.value}
className={styles.inputStyles}
onChange={
(evt)=>{
console.log("you have typed: ", evt.target.value);
}
}/>
</Validation>
入力テキストフィールド以下でonChange={this.handleChange.bind(this, "name")
メソッドとvalue={this.state.fields["name"]}
を使用して、エラーを表示するspan要素を作成します。以下の例を参照してください。
export default class Form extends Component {
constructor(){
super()
this.state ={
fields: {
name:'',
email: '',
message: ''
},
errors: {},
disabled : false
}
}
handleValidation(){
let fields = this.state.fields;
let errors = {};
let formIsValid = true;
if(!fields["name"]){
formIsValid = false;
errors["name"] = "Name field cannot be empty";
}
if(typeof fields["name"] !== "undefined" && !fields["name"] === false){
if(!fields["name"].match(/^[a-zA-Z]+$/)){
formIsValid = false;
errors["name"] = "Only letters";
}
}
if(!fields["email"]){
formIsValid = false;
errors["email"] = "Email field cannot be empty";
}
if(typeof fields["email"] !== "undefined" && !fields["email"] === false){
let lastAtPos = fields["email"].lastIndexOf('@');
let lastDotPos = fields["email"].lastIndexOf('.');
if (!(lastAtPos < lastDotPos && lastAtPos > 0 && fields["email"].indexOf('@@') === -1 && lastDotPos > 2 && (fields["email"].length - lastDotPos) > 2)) {
formIsValid = false;
errors["email"] = "Email is not valid";
}
}
if(!fields["message"]){
formIsValid = false;
errors["message"] = " Message field cannot be empty";
}
this.setState({errors: errors});
return formIsValid;
}
handleChange(field, e){
let fields = this.state.fields;
fields[field] = e.target.value;
this.setState({fields});
}
handleSubmit(e){
e.preventDefault();
if(this.handleValidation()){
console.log('validation successful')
}else{
console.log('validation failed')
}
}
render(){
return (
<form onSubmit={this.handleSubmit.bind(this)} method="POST">
<div className="row">
<div className="col-25">
<label htmlFor="name">Name</label>
</div>
<div className="col-75">
<input type="text" placeholder="Enter Name" refs="name" onChange={this.handleChange.bind(this, "name")} value={this.state.fields["name"]}/>
<span style={{color: "red"}}>{this.state.errors["name"]}</span>
</div>
</div>
<div className="row">
<div className="col-25">
<label htmlFor="exampleInputEmail1">Email address</label>
</div>
<div className="col-75">
<input type="email" placeholder="Enter Email" refs="email" aria-describedby="emailHelp" onChange={this.handleChange.bind(this, "email")} value={this.state.fields["email"]}/>
<span style={{color: "red"}}>{this.state.errors["email"]}</span>
</div>
</div>
<div className="row">
<div className="col-25">
<label htmlFor="message">Message</label>
</div>
<div className="col-75">
<textarea type="text" placeholder="Enter Message" rows="5" refs="message" onChange={this.handleChange.bind(this, "message")} value={this.state.fields["message"]}></textarea>
<span style={{color: "red"}}>{this.state.errors["message"]}</span>
</div>
</div>
<div className="row">
<button type="submit" disabled={this.state.disabled}>{this.state.disabled ? 'Sending...' : 'Send'}</button>
</div>
</form>
)
}
}
私は最近、アプリでフォームを検証するための多くのソリューションを研究するために1週間を費やしました。私はすべての最も見つめられたものから始めましたが、期待どおりに働いている人を見つけることができませんでした。数日後、非常に新しく素晴らしいプラグインを見つけるまで、私はかなりイライラしました: https://github.com/kettanaito/react-advanced-form
開発者は非常に反応がよく、彼の解決策は、私の研究の結果、私の視点から見て最も注目されるものになります。私はそれが助けになることを望み、あなたが感謝するでしょう。
過去にredux-formとformikを使用しましたが、最近ReactがHookを導入し、そのためのカスタムフックを作成しました。確認して、フォームの検証がはるかに簡単になるかどうかを確認してください。
Github: https://github.com/bluebill1049/react-hook-form
ウェブサイト: http://react-hook-form.now.sh
このアプローチを使用すると、制御された入力も行わなくなります。
以下の例:
import React from 'react'
import useForm from 'react-hook-form'
function App() {
const { register, handleSubmit, errors } = useForm() // initialise the hook
const onSubmit = (data) => { console.log(data) } // callback when validation pass
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input name="firstname" ref={register} /> {/* register an input */}
<input name="lastname" ref={register({ required: true })} /> {/* apply required validation */}
{errors.lastname && 'Last name is required.'} {/* error message */}
<input name="age" ref={register({ pattern: /\d+/ })} /> {/* apply a Refex validation */}
{errors.age && 'Please enter number for age.'} {/* error message */}
<input type="submit" />
</form>
)
}
さらに別の同じ問題に行く-form-container
on npm