React + Redux - Apa cara terbaik untuk menangani CRUD dalam komponen formulir?

128

Saya mendapatkan satu formulir yang digunakan untuk Membuat, Baca, Perbarui dan Hapus. Saya membuat 3 komponen dengan bentuk yang sama tetapi saya memberikan mereka alat peraga yang berbeda. Saya mendapat CreateForm.js, ViewForm.js (hanya baca dengan tombol hapus) dan UpdateForm.js.

Saya dulu bekerja dengan PHP, jadi saya selalu melakukan ini dalam satu bentuk.

Saya menggunakan Bereaksi dan Redux untuk mengelola toko.

Ketika saya berada di komponen CreateForm, saya meneruskan ke sub-komponen alat peraga ini createForm={true}untuk tidak mengisi input dengan nilai dan tidak menonaktifkannya. Dalam komponen ViewForm saya, saya melewati alat peraga ini readonly="readonly".

Dan saya punya masalah lain dengan textarea yang diisi dengan nilai dan tidak dapat diupdate. Bereaksi textarea dengan nilai hanya baca tetapi perlu diperbarui

Apa struktur terbaik untuk hanya memiliki satu komponen yang menangani berbagai kondisi bentuk ini?

Apakah Anda punya saran, tutorial, video, demo untuk dibagikan?

Mike Boutin
sumber

Jawaban:

115

Saya menemukan paket Formulir Redux . Itu pekerjaan yang sangat bagus!

Jadi, Anda bisa menggunakan Redux dengan React-Redux .

Pertama, Anda harus membuat komponen formulir (jelas):

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;

Setelah ini, Anda menghubungkan komponen yang menangani formulir:

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

Dan tambahkan redux-form reducer di reduksi gabungan Anda:

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;

Dan modul validator terlihat seperti ini:

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

Setelah formulir selesai, saat Anda ingin mengisi semua bidang dengan beberapa nilai, Anda dapat menggunakan initializefungsi:

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

Cara lain untuk mengisi formulir adalah dengan mengatur nilai awal.

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

Jika Anda punya cara lain untuk menangani ini, tinggalkan pesan! Terima kasih.

Mike Boutin
sumber
3
Hanya ingin tahu - apakah Anda masih menggunakan redux-forms? Saya bertanya-tanya bagaimana timbangan
pelat
2
Ya saya masih menggunakannya! Sangat bagus, saya membuat formulir yang sangat besar dan berhasil # 1. Anda hanya harus sangat berhati-hati dengan apa yang Anda lewati sebagai alat peraga untuk komponen Anda dan pembaruan mereka. Maaf atas keterlambatan jawabannya.
Mike Boutin
1
@ MikeBoutin bisakah Anda menjelaskan tentang kehati-hatian tentang alat peraga? Terima kasih
Adam K Dean
Itu worth menunjukkan bahwa bahkan dari v6.4.3, jika Anda menggunakan untuk itu potensi penuh, kinerja redux-formadalah kepalang pada semua versi IE, termasuk Edge. Jika Anda harus mendukungnya, lihat di mana saja.
Stephen Collins
2
Hanya saja harus sangat ketat dengan shouldComponentUpdate, untuk tidak membuat lag dalam formulir Anda
Mike Boutin
11

UPDATE: 2018 dan saya hanya akan menggunakan Formik (atau perpustakaan seperti Formik)

Ada juga bereaksi-redux-bentuk ( langkah-demi-langkah ), yang tampaknya bertukar beberapa redux-bentuk 's javascript (& boilerplate) dengan deklarasi markup. Kelihatannya bagus, tapi saya belum menggunakannya.

Potongan dan tempel dari readme:

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

Sunting: Perbandingan

Dokumen reaksi-redux-form menyediakan perbandingan vs redux-bentuk:

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

Ashley Coolman
sumber
4

Bagi mereka yang tidak peduli tentang perpustakaan besar untuk menangani masalah terkait formulir, saya akan merekomendasikan redux-form-utils .

Ini dapat menghasilkan nilai dan mengubah penangan untuk kontrol formulir Anda, menghasilkan reduksi formulir, pembuat tindakan praktis untuk menghapus bidang tertentu (atau semua), dll.

Yang perlu Anda lakukan adalah mengumpulkannya dalam kode Anda.

Dengan menggunakan redux-form-utils, Anda berakhir dengan manipulasi formulir seperti berikut:

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

Namun, pustaka ini hanya menyelesaikan masalah Cdan U, untuk Rdan D, mungkin Tablekomponen yang lebih terintegrasi adalah antipate.

jasonlyvia
sumber
1

Hanya hal lain bagi mereka yang ingin membuat komponen formulir yang sepenuhnya dikontrol tanpa menggunakan perpustakaan besar.

ReduxFormHelper - kelas ES6 kecil, kurang dari 100 baris:

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}
  }
}

Itu tidak melakukan semua pekerjaan untuk Anda. Namun itu memfasilitasi pembuatan, validasi dan penanganan komponen formulir yang dikendalikan. Anda bisa saja menyalin & menempelkan kode di atas ke proyek Anda atau sebaliknya, memasukkan pustaka yang terkait - redux-form-helper(plug!).

Cara Penggunaan

Langkah pertama adalah menambahkan data spesifik ke status Redux yang akan mewakili status formulir kami. Data ini akan menyertakan nilai bidang saat ini serta set bendera kesalahan untuk setiap bidang dalam formulir.

Status formulir dapat ditambahkan ke peredam yang ada atau didefinisikan dalam peredam yang terpisah.

Selain itu, perlu untuk menentukan tindakan spesifik yang memulai pembaruan status formulir serta pembuat tindakan masing-masing.

Contoh tindakan :

export const FORM_UPDATE = 'FORM_UPDATE' 

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

Contoh peredam :

...
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 || {})
      }
    ...
  }
}

Langkah kedua dan terakhir adalah membuat komponen kontainer untuk formulir kami dan menghubungkannya dengan bagian dan tindakan Redux masing-masing.

Kita juga perlu mendefinisikan model formulir yang menetapkan validasi bidang formulir. Sekarang kita instantiate ReduxFormHelperobjek sebagai anggota komponen dan lulus di sana model formulir kami dan pembaruan pengiriman panggilan balik dari status formulir.

Kemudian pada komponen render()metode kita harus mengikat masing-masing bidang ini onChangedan form onSubmitacara dengan processField()dan processForm()metode masing-masing serta tampilan blok kesalahan untuk setiap bidang tergantung pada bendera bentuk kesalahan di negara bagian.

Contoh di bawah ini menggunakan CSS dari kerangka Bootstrap Twitter.

Contoh Komponen Kontainer :

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)

Demo

paling belakang
sumber