React + Redux-Quelle est la meilleure façon de gérer CRUD dans un composant de forme?

j'ai reçu un formulaire qui est utilisé pour créer, Lire, mettre à jour et supprimer. J'ai créé 3 composants avec la même forme mais je leur passe des accessoires différents. J'ai eu CreateForm.js, ViewForm.js (readonly avec le bouton supprimer) et UpdateForm.js.

j'avais l'habitude de travailler avec PHP, donc je l'ai toujours fait sous une forme.

J'utilise React et Redux pour gérer le magasin.

quand je suis dans le composant CreateForm, je passe à mes sous-composants props createForm={true} pour ne pas remplir les entrées avec une valeur et ne pas les désactiver. Dans mon composant ViewForm, je passe ce props readonly="readonly" .

Et j'ai eu un autre problème avec un textarea qui est rempli avec une valeur et n'est pas modifiable. Réagir textarea avec la valeur est en lecture seule, mais doivent être mis à jour

Quelle est la meilleure structure pour n'avoir qu'un seul composant qui gère ces différents états de la forme?

avez-vous des conseils, tutoriels, vidéos, démos à partager?

121
demandé sur Talha Awan 2015-10-20 16:20:59

4 réponses

j'ai trouvé le paquet Redux Form . Il fait un très bon travail!

ainsi, vous pouvez utiliser Redux avec React-Redux .

vous devez d'abord créer un composant de formulaire (évidemment):

import React from 'react';
import { reduxForm } from 'redux-form';
import validateContact from '../utils/validateContact';

class ContactForm extends React.Component {
  render() {
    const { fields: {name, address, phone}, handleSubmit } = this.props;
    return (
      <form onSubmit={handleSubmit}>
        <label>Name</label>
        <input type="text" {...name}/>
        {name.error && name.touched && <div>{name.error}</div>}

        <label>Address</label>
        <input type="text" {...address} />
        {address.error && address.touched && <div>{address.error}</div>}

        <label>Phone</label>
        <input type="text" {...phone}/>
        {phone.error && phone.touched && <div>{phone.error}</div>}

        <button onClick={handleSubmit}>Submit</button>
      </form>
    );
  }
}

ContactForm = reduxForm({
  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 default ContactForm;

après cela, vous connectez le composant qui gère le formulaire:

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

class App extends React.Component {

  handleSubmit(data) {
    console.log('Submission received!', data);
    this.props.dispatch(initialize('contact', {})); // clear form
  }

  render() {
    return (
      <div id="app">
        <h1>App</h1>
        <ContactForm onSubmit={this.handleSubmit.bind(this)}/>
      </div>
    );
  }

}

export default connect()(App);

et ajouter le réducteur redux-form dans votre réducteurs combinés:

import { combineReducers } from 'redux';
import { appReducer } from './app-reducers';
import { reducer as formReducer } from 'redux-form';

let reducers = combineReducers({
  appReducer, form: formReducer // this is the form reducer
});

export default reducers;

et le module de validation ressemble à ceci:

export default function validateContact(data, props) {
  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;
}

une fois le formulaire rempli, si vous voulez remplir tous les champs avec quelques valeurs, vous pouvez utiliser la fonction initialize :

componentWillMount() {
  this.props.dispatch(initialize('contact', {
    name: 'test'
  }, ['name', 'address', 'phone']));
}

une autre façon de remplir les formulaires est de définir les valeurs initiales.

ContactForm = reduxForm({
  form: 'contact',                      // the name of your form and the key to
  fields: ['name', 'address', 'phone'], // a list of all your fields in your form
  validate: validateContact             // a synchronous validation function
}, state => ({
  initialValues: {
    name: state.user.name,
    address: state.user.address,
    phone: state.user.phone,
  },
}))(ContactForm);

si vous avez un autre moyen de gérer ça, laissez un message! Remercier.

112
répondu Mike Boutin 2016-03-22 17:04:45

mise à JOUR: sa 2018 et je ne vais jamais utiliser Formik (ou Formik-comme les bibliothèques)

Il y a aussi réagissent-redux-forme ( étape par étape ), qui semble échanger des redux-forme javascript (et standard) avec le balisage de la déclaration. Il semble bien, mais je n'ai pas encore utilisé.

couper et coller dans le fichier lisez-moi:

import React from 'react';
import { createStore, combineReducers } from 'redux';
import { Provider } from 'react-redux';
import { modelReducer, formReducer } from 'react-redux-form';

import MyForm from './components/my-form-component';

const store = createStore(combineReducers({
  user: modelReducer('user', { name: '' }),
  userForm: formReducer('user')
}));

class App extends React.Component {
  render() {
    return (
      <Provider store={ store }>
        <MyForm />
      </Provider>
    );
  }
}

./components/my-form-component.js

import React from 'react';
import { connect } from 'react-redux';
import { Field, Form } from 'react-redux-form';

class MyForm extends React.Component {
  handleSubmit(val) {
    // Do anything you want with the form value
    console.log(val);
  }

  render() {
    let { user } = this.props;

    return (
      <Form model="user" onSubmit={(val) => this.handleSubmit(val)}>
        <h1>Hello, { user.name }!</h1>
        <Field model="user.name">
          <input type="text" />
        </Field>
        <button>Submit!</button>
      </Form>
    );
  }
}

export default connect(state => ({ user: state.user }))(MyForm);

Edit: "Comparaison Des 151960920"

La réaction-redux-forme docs de fournir une comparaison vs redux-forme:

https://davidkpiano.github.io/react-redux-form/docs/guides/compare-redux-form.html

10
répondu Ashley Coolman 2018-04-25 07:28:58

pour ceux qui ne se soucient pas d'une énorme bibliothèque pour traiter les questions liées aux formulaires, je recommande redux-form-utils .

il peut générer de la valeur et changer des gestionnaires pour vos contrôles de forme, générer des réducteurs de la forme, des créateurs d'action pratiques pour effacer certains(ou tous) champs, etc.

Tout ce que vous devez faire est de les assembler dans votre code.

en utilisant redux-form-utils , vous vous retrouvez avec le formulaire manipulation comme suit:

import { createForm } from 'redux-form-utils';

@createForm({
  form: 'my-form',
  fields: ['name', 'address', 'gender']
})
class Form extends React.Component {
  render() {
    const { name, address, gender } = this.props.fields;
    return (
      <form className="form">
        <input name="name" {...name} />
        <input name="address" {...address} />
        <select {...gender}>
          <option value="male" />
          <option value="female" />
        </select>
      </form>
    );
  }
}

cependant, cette bibliothèque ne résout problème C et U , pour R et D , peut-être un composant plus intégré Table est à antipate.

4
répondu jasonslyvia 2016-06-07 09:14:48

juste une autre chose pour ceux qui veulent créer un composant de forme entièrement contrôlé sans utiliser une bibliothèque surdimensionnée.

ReduxFormHelper - une petite classe ES6, moins de 100 lignes:

class ReduxFormHelper {
  constructor(props = {}) {
    let {formModel, onUpdateForm} = props
    this.props = typeof formModel === 'object' &&
      typeof onUpdateForm === 'function' && {formModel, onUpdateForm}
  }

  resetForm (defaults = {}) {
    if (!this.props) return false
    let {formModel, onUpdateForm} = this.props
    let data = {}, errors = {_flag: false}
    for (let name in formModel) {
      data[name] = name in defaults? defaults[name] :
        ('default' in formModel[name]? formModel[name].default : '')
      errors[name] = false
    }
    onUpdateForm(data, errors)
  }

  processField (event) {
    if (!this.props || !event.target) return false
    let {formModel, onUpdateForm} = this.props
    let {name, value, error, within} = this._processField(event.target, formModel)
    let data = {}, errors = {_flag: false}
    if (name) {
      value !== false && within && (data[name] = value)
      errors[name] = error
    }
    onUpdateForm(data, errors)
    return !error && data
  }

  processForm (event) {
    if (!this.props || !event.target) return false
    let form = event.target
    if (!form || !form.elements) return false
    let fields = form.elements
    let {formModel, onUpdateForm} = this.props
    let data = {}, errors = {}, ret = {}, flag = false
    for (let n = fields.length, i = 0; i < n; i++) {
      let {name, value, error, within} = this._processField(fields[i], formModel)
      if (name) {
        value !== false && within && (data[name] = value)
        value !== false && !error && (ret[name] = value)
        errors[name] = error
        error && (flag = true)
      }
    }
    errors._flag = flag
    onUpdateForm(data, errors)
    return !flag && ret
  }

  _processField (field, formModel) {
    if (!field || !field.name || !('value' in field))
      return {name: false, value: false, error: false, within: false}
    let name = field.name
    let value = field.value
    if (!formModel || !formModel[name])
      return {name, value, error: false, within: false}
    let model = formModel[name]
    if (model.required && value === '')
      return {name, value, error: 'missing', within: true}
    if (model.validate && value !== '') {
      let fn = model.validate
      if (typeof fn === 'function' && !fn(value))
        return {name, value, error: 'invalid', within: true}
    }
    if (model.numeric && isNaN(value = Number(value)))
      return {name, value: 0, error: 'invalid', within: true}
    return {name, value, error: false, within: true}
  }
}

il ne fait pas tout le travail pour vous. Toutefois, il facilite la création, la validation et la manipulation d'un composant de forme contrôlée. Vous pouvez simplement copier & coller le code ci-dessus dans votre projet ou à la place, inclure le bibliothèque respective- redux-form-helper (branchez!).

comment utiliser

la première étape est d'ajouter des données spécifiques à L'état de Redux qui représenteront l'état de notre forme. Ces données incluront les valeurs actuelles des champs ainsi que des indicateurs d'erreur pour chaque champ du formulaire.

l'état de forme peut être ajouté à un réducteur existant ou défini dans un réducteur séparé.

de plus, c'est nécessaire pour définir action spécifique initiant la mise à jour de l'état de forme ainsi que créateur d'action respectif.

exemple D'Action :

export const FORM_UPDATE = 'FORM_UPDATE' 

export const doFormUpdate = (data, errors) => {
  return { type: FORM_UPDATE, data, errors }
}
...

exemple de réducteur :

...
const initialState = {
  formData: {
    field1: '',
    ...
  },
  formErrors: {
  },
  ...
}

export default function reducer (state = initialState, action) {
  switch (action.type) {
    case FORM_UPDATE:
      return {
        ...ret,
        formData: Object.assign({}, formData, action.data || {}),
        formErrors: Object.assign({}, formErrors, action.errors || {})
      }
    ...
  }
}

la deuxième et dernière étape est de créer un composant conteneur pour notre forme et de le connecter avec la partie respective de L'état et des actions de Redux.

nous devons aussi définir un modèle de formulaire spécifiant la validation des champs de formulaire. Maintenant nous instancions l'objet ReduxFormHelper en tant que membre du component et nous y passons notre modèle de formulaire et une mise à jour de l'état de formulaire par envoi de callback.

ensuite, dans la méthode render() du composant, nous devons lier les événements onChange et onSubmit du formulaire avec les méthodes processField() et processForm() respectivement, ainsi que les blocs d'erreur d'affichage pour chaque champ selon l'erreur du formulaire. les drapeaux de l'état.

l'exemple ci-dessous utilise CSS de Twitter Bootstrap framework.

conteneur Component example :

import React, {Component} from 'react';
import {connect} from 'react-redux'
import ReduxFormHelper from 'redux-form-helper'

class MyForm extends Component {
  constructor(props) {
    super(props);
    this.helper = new ReduxFormHelper(props)
    this.helper.resetForm();
  }

  onChange(e) {
    this.helper.processField(e)
  }

  onSubmit(e) {
    e.preventDefault()
    let {onSubmitForm} = this.props
    let ret = this.helper.processForm(e)
    ret && onSubmitForm(ret)
  }

  render() {
    let {formData, formErrors} = this.props
    return (
  <div>
    {!!formErrors._flag &&
      <div className="alert" role="alert">
        Form has one or more errors.
      </div>
    }
    <form onSubmit={this.onSubmit.bind(this)} >
      <div className={'form-group' + (formErrors['field1']? ' has-error': '')}>
        <label>Field 1 *</label>
        <input type="text" name="field1" value={formData.field1} onChange={this.onChange.bind(this)} className="form-control" />
        {!!formErrors['field1'] &&
        <span className="help-block">
          {formErrors['field1'] === 'invalid'? 'Must be a string of 2-50 characters' : 'Required field'}
        </span>
        }
      </div>
      ...
      <button type="submit" className="btn btn-default">Submit</button>
    </form>
  </div>
    )
  }
}

const formModel = {
  field1: {
    required: true,
    validate: (value) => value.length >= 2 && value.length <= 50
  },
  ...
}

function mapStateToProps (state) {
  return {
    formData: state.formData, formErrors: state.formErrors,
    formModel
  }
}

function mapDispatchToProps (dispatch) {
  return {
    onUpdateForm: (data, errors) => {
      dispatch(doFormUpdate(data, errors))
    },
    onSubmitForm: (data) => {
      // dispatch some action which somehow updates state with form data
    }
  }
}

export default connect(mapStateToProps, mapDispatchToProps)(MyForm)

Démo

1
répondu hindmost 2017-09-18 10:37:41