web-dev-qa-db-ja.com

redux-formバインディングをフォームの入力に結び付ける方法

redux-form は、reactアプリケーションのフォームにreduxバインディングを提供するための非常に魅力的なライブラリであり、非常に便利です。残念ながら、ライブラリの独自の例を使用すると、実際には何もバインドできません。これは非常に不便です。

プロジェクトサイトでサンプルコードを使用し、忠実に再現しようとしているにもかかわらず、複数の障害を見つけようとしています。このAPIをどこで誤解していますか?デモコードが作成されてからAPIは変更されましたか?重要な明白なリデュースの知識が欠けていますか?

問題1:handleSubmitメソッドの署名はhandleSubmit(data)である必要があります。ただし、handleSubmitは現在、送信アクションからReact syntheticEventのみを受信して​​おり、データは受信していません(実際、記述された例を使用すると、スタックされたonSubmitアクションのために、フォーム上のボタンとボタン上のonClick。)そのデータはどこから来ているのでしょうか。なぜハンドラーに渡さないのですか?

問題2:フォームの親で定義し、フォームにpropとして提供する必要がある重要なfieldsオブジェクトがあります。残念ながら、そのfieldsオブジェクトの形状は、ドキュメントでもその目的でも説明されていません。それは本質的に初期の「状態」オブジェクトですか?エラーなどのために実行時に使用するredux-formの単純なオブジェクトコンテナーですか? fieldsの小道具をconnectReduxFormのフィールド名に一致させることでエラーを停止しましたが、データがバインドされていないため、正しい形状ではないと仮定しています。

問題3:フィールドは、onBlurおよびonChangeのハンドラーに自動的にバインドされるため、ストアが適切に更新されます。それは決して起こらない。 (これはRedux開発ツールのおかげです。ただし、handleSubmitinitializeアクションを正常にディスパッチしています。これは、ストア、レデューサー、およびその他の基本的な配管がすべて機能していることを示しています。)

問題4validateContactはinitで1回起動しますが、再び起動することはありません。

これは残念ながら単純なFiddleには複雑すぎますが、レポ全体(基本的なReduxStarterAppとこのフォームPOCのみ) こちらから入手可能 です。

そして、これが外部コンポーネントです。

import React       from 'react';
import { connect } from 'react-redux';
import {initialize} from 'redux-form';

import ContactForm from '../components/simple-form/SimpleForm.js';

const mapStateToProps = (state) => ({
  counter : state.counter
});
export class HomeView extends React.Component {
  static propTypes = {
    dispatch : React.PropTypes.func.isRequired,
    counter  : React.PropTypes.number
  }

  constructor () {
    super();
  }
  handleSubmit(event, data) {
    event.preventDefault();
    console.log(event); // this should be the data, but is an event
    console.log(data); // no data here, either...
    console.log('Submission received!', data);
    this.props.dispatch(initialize('contact', {})); // clear form: THIS works
    return false;
  }

  _increment () {
    this.props.dispatch({ type : 'COUNTER_INCREMENT' });
  }


  render () {
    const fields = {
      name: '',
      address: '',
      phone: ''
    };

    return (
      <div className='container text-center'>
        <h1>Welcome to the React Redux Starter Kit</h1>
        <h2>Sample Counter: {this.props.counter}</h2>
        <button className='btn btn-default'
                onClick={::this._increment}>
          Increment
        </button>
        <ContactForm handleSubmit={this.handleSubmit.bind(this)} fields={fields} />
      </div>
    );
  }
}

export default connect(mapStateToProps)(HomeView);

そして、内部フォームコンポーネント:

import React, {Component, PropTypes} from 'react';
import {connectReduxForm} from 'redux-form';

function validateContact(data) {
  console.log("validating");
  console.log(data);
  const errors = {};
  if (!data.name) {
    errors.name = 'Required';
  }
  if (data.address && data.address.length > 50) {
    errors.address = 'Must be fewer than 50 characters';
  }
  if (!data.phone) {
    errors.phone = 'Required';
  } else if (!/\d{3}-\d{3}-\d{4}/.test(data.phone)) {
    errors.phone = 'Phone must match the form "999-999-9999"';
  }
  return errors;
}

class ContactForm extends Component {
  static propTypes = {
    fields: PropTypes.object.isRequired,
    handleSubmit: PropTypes.func.isRequired
  }

  render() {
    const { fields: {name, address, phone}, handleSubmit } = this.props;
    return (
      <form onSubmit={handleSubmit}>
        <label>Name</label>
        <input type="text" {...name}/>     {/* will pass value, onBlur and onChange */}
        {name.error && name.touched && <div>{name.error}</div>}

        <label>Address</label>
        <input type="text" {...address}/>  {/* will pass value, onBlur and onChange*/}
        {address.error && address.touched && <div>{address.error}</div>}

        <label>Phone</label>
        <input type="text" {...phone}/>    {/* will pass value, onBlur and onChange */}
        {phone.error && phone.touched && <div>{phone.error}</div>}

        <button type='submit'>Submit</button>
      </form>
    );
  }
}

// apply connectReduxForm() and include synchronous validation
ContactForm = connectReduxForm({
  form: 'contact',                      // the name of your form and the key to
                                        // where your form's state will be mounted
  fields: ['name', 'address', 'phone'], // a list of all your fields in your form
  validate: validateContact             // a synchronous validation function
})(ContactForm);

// export the wrapped component
export default ContactForm;
28
XML

connectReduxFormは、fieldsおよびhandleSubmitの小道具を渡すことを処理する別のコンポーネントでコンポーネントをラップしますが、それらを自分で渡すことでそれらを吹き飛ばしています。

代わりにこれを試してください(小道具の名前をonSubmitに変更):

<ContactForm onSubmit={this.handleSubmit.bind(this)}/>

ContactFormで、 redux-form が提供するhandleSubmit関数に独自の送信ハンドラを渡します。

<form onSubmit={handleSubmit(this.props.onSubmit)}>

React開発者ツール を使用して、何が起こっているかをよりよく把握することをお勧めします-redux-formがコンポーネントをラップする方法と README で文書化されているように、小道具の束全体を渡します。

redux-form composition in React developer tools

23
Jonny Buchanan

最も重要な点を説明してくれたJonny Buchananに感謝します。私がやったようにしないでください。コンポーネントに小道具が必要な場合は、自分で提供する必要があると自動的に仮定します。 connectReduxFormである高階関数のポイントは、ラッパーコンポーネントで提供することです。これを修正すると、Submit以外のすべてのイベントハンドラーがすぐに得られました。

他の重要な監視はここにありました:

注–自分でconnect()を実行していない場合(必要な高度なユースケースがない限り、実行しないことをお勧めします)、formにリデューサーをマウントする必要があります。

私はその点を理解できませんでした。ただし、実装は次のとおりです。

import { createStore, combineReducers } from 'redux';
import { reducer as formReducer } from 'redux-form';
const reducers = {
  // ... your other reducers here ...
  form: formReducer           // <---- Mounted at 'form'
}
const reducer = combineReducers(reducers);
const store = createStore(reducer);

FormReducerはformReducerで参照できませんが、構文form: formReducerが必要です。これは、handleSubmitを適切に有効化した修正でした。

8
XML