web-dev-qa-db-fra.com

Comment câbler des liaisons redux-form aux entrées du formulaire

redux-form est une bibliothèque très convaincante pour fournir des liaisons redux pour les formulaires dans une application React, ce qui devrait être super pratique. Malheureusement, en utilisant les propres exemples de la bibliothèque, je n'arrive pas à lier quoi que ce soit, ce qui est super pratique.

J'essaie d'utiliser l'exemple de code sur le site du projet et de trouver plusieurs obstacles, malgré une tentative de le reproduire fidèlement. Où est-ce que j'interprète mal cette API? L'API a-t-elle changé depuis l'écriture du code de démonstration? Suis-je en train de manquer un élément essentiel et évident de connaissances redux?

Problème 1 : la signature de la méthode handleSubmit doit être handleSubmit(data). Mais handleSubmit ne reçoit actuellement que le React synthétiqueEvent de l'action de soumission, et aucune donnée. (En fait, utiliser l'exemple tel qu'écrit envoyait deux événements distincts, apparemment à cause du _ onSubmit action sur le formulaire et onClick sur le bouton.) D'où ces données sont-elles censées provenir, et pourquoi ne parviens-je pas à les transmettre au gestionnaire?

Problème 2 : il y a un objet fields critique qui doit être défini sur le parent du formulaire et fourni comme accessoire à votre formulaire. Malheureusement, la forme de cet objet fields n'est pas vraiment expliquée dans les documents, ni son objectif. S'agit-il essentiellement de l'objet "état" initial? Un simple conteneur d'objets pour redux-form à utiliser lors de l'exécution pour les erreurs, etc? Je l'ai fait pour arrêter l'erreur en faisant correspondre les accessoires sur fields aux noms de champ dans connectReduxForm, mais parce que les données ne sont pas contraignantes, je suppose que ce n'est pas la bonne forme.

Problème 3 : Les champs sont censés être liés automatiquement aux gestionnaires pour onBlur et onChange, afin qu'ils mettre à jour le magasin de manière appropriée. Cela n'arrive jamais. (Ce que nous pouvons voir grâce aux outils de développement Redux. Cependant, handleSubmit envoie avec succès l'action initialize, ce qui suggère que le magasin, le réducteur et d'autres éléments de plomberie de base fonctionnent tous.)

Problème 4 : validateContact se déclenche une fois sur init, mais plus jamais.

C'est malheureusement trop complexe pour un simple violon, mais le repo entier (c'est juste le ReduxStarterApp de base, plus ce formulaire POC) est disponible ici .

Et, voici le composant extérieur:

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);

Et le composant de formulaire interne:

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 encapsule votre composant avec un autre composant qui gère le passage des accessoires fields et handleSubmit, mais vous les faites exploser en les passant vous-même.

Essayez ceci à la place (renommé le prop en onSubmit):

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

Et dans ContactForm, passez votre propre gestionnaire de soumission à la fonction handleSubmit fournie par redux-form :

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

Je recommande d'utiliser React developer tools pour avoir une meilleure idée de ce qui se passe - vous verrez comment redux-form encapsule votre composant et lui transmet tout un tas d'accessoires, comme documenté dans son README .

redux-form composition in React developer tools

23
Jonny Buchanan

Merci à Jonny Buchanan, qui a couvert le point le plus important: ne faites pas comme moi et supposez automatiquement que si des accessoires sont requis dans votre composant, vous devez les fournir vous-même. L'intérêt de la fonction d'ordre supérieur qui est connectReduxForm est de les fournir dans le composant wrapper. Correction qui m'a immédiatement donné des gestionnaires d'événements, pour tout sauf Soumettre.

L'autre surveillance critique était ici:

REMARQUE - Si vous n'effectuez pas la connexion () vous-même (et il est recommandé de ne pas le faire, sauf si vous avez un cas d'utilisation avancée qui l'exige), vous devez monter le réducteur à formulaire.

Je ne l'ai pas compris. Mais l'implémentation est là:

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);

Le formReducer ne peut pas être référencé à formReducer, mais nécessite la syntaxe form: formReducer. C'est la correction qui a correctement activé handleSubmit.

8
XML