React + Redux - Який найкращий спосіб обробити CRUD у складі форми?


128

У мене з'явилася одна форма, яка використовується для створення, читання, оновлення та видалення. Я створив 3 компоненти з тією ж формою, але передаю їм різні реквізити. Я отримав CreateForm.js, ViewForm.js (читати лише за допомогою кнопки видалення) та UpdateForm.js.

Раніше я працював з PHP, тому завжди робив це в одній формі.

Я використовую React і Redux для управління магазином.

Коли я перебуваю в компоненті CreateForm, я передаю своїм підкомпонентам цей реквізит, createForm={true}щоб не заповнювати входи значенням і не відключати їх. У своєму компоненті ViewForm я передаю цей реквізит readonly="readonly".

І у мене з'явилася ще одна проблема з текстовим текстом, який заповнений значенням і не оновлюється. Реагувати текстові області зі значенням читаються лише одразу, але потребують оновлення

Яка найкраща структура має лише один компонент, який обробляє ці різні стани форми?

Чи маєте якісь поради, навчальні посібники, відео, демонстрації?

Відповіді:


115

Я знайшов форму Redux пакет . Це справді гарна робота!

Отже, ви можете використовувати Redux з React-Redux .

Спочатку потрібно створити компонент форми (очевидно):

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;

Після цього ви підключаєте компонент, який обробляє форму:

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

І додайте редуктор у формі комбінації у ваші комбіновані редуктори:

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;

А модуль валідатора виглядає так:

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

Після заповнення форми, коли ви хочете заповнити всі поля деякими значеннями, ви можете скористатися initializeфункцією:

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

Ще один спосіб заповнення форм - це встановлення початкових Значень.

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

Якщо у вас є інший спосіб впоратися з цим, просто залиште повідомлення! Дякую.


3
Просто цікаво - ти все ще використовуєш redux-forms? Мені цікаво, як те, що масштабує
котла,

2
Так, я все ще використовую його! Дуже приємно, я створив дуже великі форми, і це працювало №1. Ви просто повинні бути дуже обережними з тим, що ви передаєте в якості реквізиту до своїх компонентів та їх оновлень. Вибачте за затримку відповіді.
Майк Бутін

1
@MikeBoutin Ви могли б детальніше розглянути цю обережність щодо реквізиту? Спасибі
Адам К Дін

Варто зазначити, що навіть з версії 6.4.3, якщо ви використовуєте її повний потенціал, продуктивність redux-formне відповідає нормам для всіх версій IE, включаючи Edge. Якщо вам доведеться його підтримати, подивіться кудись.
Стівен Коллінз

2
Це просто бути дуже суворим з shouldComponentUpdate, щоб не створювати затримок у ваших формах
Майк Бутін

11

ОНОВЛЕННЯ: його 2018 рік, і я коли-небудь буду використовувати Formik (або бібліотеки, подібні до Formik)

Існує також реакція-редукція-форма ( крок за кроком ), яка, здається, обміняється частиною редук-форми javascript (& plateplate) редуксної форми з декларацією розмітки. Це добре виглядає, але я його ще не використовував.

Виріжте і вставте з 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);

Правка: Порівняння

Документи "Реакція-редукція" забезпечують порівняння з формою редуксії:

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


4

Для тих, хто не переймається величезною бібліотекою для вирішення проблем, пов’язаних із формою, я рекомендую утиліти redux-form .

Він може генерувати обробники значень та змінювати обробники ваших елементів керування формою, генерувати редуктори форми, зручні творці дій для очищення певних (або всіх) полів тощо.

Все, що вам потрібно зробити, - зібрати їх у свій код.

Використовуючи redux-form-utils, ви закінчуєте маніпулювання формою, як:

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

Тим НЕ менше, ця бібліотека вирішує тільки проблему Cі U, для Rі D, можливо, більш інтегрований Tableкомпонент є antipate.


1

Ще одна річ для тих, хто хоче створити повністю керований компонент форми без використання великої бібліотеки.

ReduxFormHelper - невеликий клас ES6, менше 100 рядків:

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

Це не робить всю роботу за вас. Однак це полегшує створення, перевірку та обробку компонента керованої форми. Ви можете просто скопіювати та вставити вищевказаний код у свій проект або замість цього включити відповідну бібліотеку -redux-form-helper (підключіть!).

Як використовувати

Перший крок - додавання конкретних даних до стану Redux, які представлятимуть стан нашої форми. Ці дані включатимуть поточні значення поля, а також набір прапорів помилок для кожного поля у формі.

Стан форми може бути доданий до існуючого редуктора або визначений окремим редуктором.

Крім того, необхідно визначити конкретні дії, що ініціюють оновлення стану форми, а також відповідного творця дій.

Приклад дії :

export const FORM_UPDATE = 'FORM_UPDATE' 

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

Приклад редуктора :

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

Другий і останній крок - це створити компонент контейнера для нашої форми і з'єднати його з відповідною частиною стану Redux і дій.

Також нам потрібно визначити модель форми із зазначенням валідації полів форми. Тепер ми створюємо ReduxFormHelperоб'єкт як член компонента і передаємо туди нашу форму форми та зворотний виклик, що відправляє оновлення стану форми.

Тоді в render()методі компонента ми повинні зв’язати події кожного поля onChangeта форми відповідно onSubmitз методами processField()та processForm()методами відповідно, а також відобразити блоки помилок для кожного поля залежно від прапорів помилок форми у державі.

У наведеному нижче прикладі використовується CSS з фреймворку Twitter Bootstrap.

Приклад компонента контейнера :

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)

Демо

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.