如何将 redux-form 绑定连接到表单的输入

How to wire up redux-form bindings to the form's inputs

redux-form 是一个非常引人注目的库,用于为 React 应用程序中的表单提供 redux 绑定,这应该非常方便。不幸的是,使用库自己的示例,我无法真正绑定任何东西,这非常不方便。

我正在尝试使用项目站点上的示例代码,并发现了多个障碍,尽管我试图忠实地重现它。我在哪里误解了这个 API? API 自演示代码编写以来是否发生了变化?我是否遗漏了一些重要且明显的 redux 知识?

问题 1:handleSubmit 方法的签名应该是 handleSubmit(data)。但是 handleSubmit 目前只接收来自提交操作的 React syntheticEvent,没有数据。 (事实上​​,使用编写的示例发送两个单独的事件,似乎是因为表单上的堆叠 onSubmit 操作和按钮上的 onClick。)数据应该来自哪里来自,为什么我没有将它传递给处理程序?

问题 2:有一个关键的 fields 对象必须在父表单上定义并作为 prop 提供给您的表单。不幸的是,fields 对象的形状并没有在文档中解释,也没有真正解释它的目的。它本质上是初始的 'state' 对象吗?一个用于 redux-form 的简单对象容器,用于在运行时处理错误等?我已经通过将 fields 上的道具与 connectReduxForm 中的字段名称相匹配来停止错误,但是因为数据没有绑定,我假设它的形状不正确。

问题 3:字段应该自动绑定到 onBluronChange 的处理程序,以便它们适当地更新存储。那永远不会发生。 (感谢 Redux 开发工具,我们可以看到这一点。但是,handleSubmit 正在成功调度 initialize 操作,这表明存储、reducer 和其他基本管道都在工作。)

问题 4validateContact 在初始化时触发一次,但再也不会触发。

不幸的是,这对于一个简单的 Fiddle 来说太复杂了,但是整个 repo(它只是基本的 ReduxStarterApp,加上这个表单 POC)is available here

而且,这是外部组件:

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;

connectReduxForm 用另一个处理传入 fieldshandleSubmit 道具的组件包装你的组件,但你通过自己传递它们来吹走它们。

试试这个(将道具重命名为 onSubmit):

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

并且在 ContactFormpass your own submit handler to the handleSubmit function provided by redux-form:

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

我建议使用 React developer tools to get a better picture of what's going on - you'll see how redux-form wraps your component and passes it a whole bunch of props, as documented in its README

感谢 Jonny Buchanan,他涵盖了最重要的一点:不要像我那样做并自动假设如果组件中需要 props,您必须自己提供它们。 connectReduxForm 的高阶函数的全部意义在于在包装器组件中提供它们。解决这个问题立即给了我事件处理程序,除了提交之外的所有内容。

另一个重要的疏忽是:

NOTE – If you are not doing the connect()ing yourself (and it is recommended that you do not, unless you have an advanced use case that requires it), you must mount the reducer at 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 的更正。