React + Redux-каков наилучший способ обработки CRUD в компоненте формы?
у меня есть одна форма, которая используется для создания, чтения, обновления и удаления. Я создал 3 компонента с той же формой, но я передаю им разные реквизиты. Я получил CreateForm.js, ViewForm.js (только для чтения с помощью кнопки Удалить) и UpdateForm.js.
раньше я работал с PHP, поэтому я всегда делал это в одной форме.
Я использую React и Redux для управления магазином.
когда я нахожусь в компоненте CreateForm, я передаю своим подкомпонентам этот реквизит createForm={true} чтобы не заполнить входы со значением и не отключайте их. В моем компоненте ViewForm я передаю этот реквизит readonly="readonly".
и у меня есть еще одна проблема с текстовой областью, которая заполнена значением и не обновляется. Реагировать textarea со значением только для чтения, но должны быть обновлены
какова лучшая структура, чтобы иметь только один компонент, который обрабатывает эти различные состояния формы?
есть ли у вас какие-либо советы, учебники, видео, демонстрации, чтобы поделиться?
4 ответов:
нашел Redux Form пакета. Это действительно хорошая работа!
Итак, вы можете использовать возвращение С 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);и добавьте редуктор redux-формы в ваших совмещенных редукторах:
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);если у вас есть какой-либо другой способ справиться с этим, просто оставьте сообщение! Спасибо.
обновление: его 2018, и я буду использовать только Формик (или Formik-подобные библиотеки)
есть еще react-redux-form (шаг за шагом), который, кажется, обменять redux-formjavascript (&boilerplate) с объявлением разметки. Это выглядит хорошо, но я еще не использовал его.
вырезать и вставить из 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.jsimport 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);изменить: Сравнение
реагировать-обертывание-форма документы предоставляем сравнение против Multiplayer-в форме:
https://davidkpiano.github.io/react-redux-form/docs/guides/compare-redux-form.html
для тех, кто не заботится об огромной библиотеке для обработки вопросов, связанных с формой, я бы рекомендовал redux-form-utils.
он может генерировать значение и изменять обработчики для ваших элементов управления формой, генерировать редукторы формы, удобные создатели действий для очистки определенных (или всех) полей и т. д.
все, что вам нужно сделать, это собрать их в коде.
С помощью
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компонент должен антипатировать.
просто еще одна вещь для тех, кто хочет создать полностью контролируемый компонент, без использования крупногабаритных библиотека.
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(plug!).как использовать
первым шагом является добавление конкретных данных в состояние 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 framework.
Компонент-Контейнер пример:
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)
Comments