javascript - tutorial - react crud generator
React+Redux: ¿cuál es la mejor manera de manejar CRUD en un componente de formulario? (4)
ACTUALIZACIÓN: es 2018 y solo Formik (o bibliotecas similares a Formik)
También hay react-redux-form ( step-by-step ), que parece intercambiar parte del javascript de redux-form (& boilerplate) con la declaración de marcado. Se ve bien, pero aún no lo he usado.
Un cortar y pegar del archivo Léame:
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);
Editar: Comparación
Los documentos react-redux-form proporcionan una comparación frente a la forma redux:
https://davidkpiano.github.io/react-redux-form/docs/guides/compare-redux-form.html
Tengo un formulario que está acostumbrado a Crear, Leer, Actualizar y Eliminar. Creé 3 componentes con la misma forma pero les paso diferentes accesorios. Obtuve CreateForm.js, ViewForm.js (solo lectura con el botón Eliminar) y UpdateForm.js.
Solía trabajar con PHP, así que siempre hacía esto de una forma.
Uso React y Redux para administrar la tienda.
Cuando estoy en el componente CreateForm, paso a mis subcomponentes estos accesorios
createForm={true}
para no llenar las entradas con un valor y no deshabilitarlas.
En mi componente ViewForm, paso estos accesorios
readonly="readonly"
.
Y tengo otro problema con un área de texto que está lleno de un valor y no es actualizable. Reaccionar el área de texto con valor es de solo lectura, pero debe actualizarse
¿Cuál es la mejor estructura para tener un solo componente que maneje estos diferentes estados del formulario?
¿Tienes algún consejo, tutoriales, videos, demos para compartir?
Encontré el paquete Redux Form . ¡Hace un muy buen trabajo!
Entonces, puedes usar Redux con React-Redux .
Primero tienes que crear un componente de formulario (obviamente):
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;
Después de esto, conecta el componente que maneja el formulario:
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);
Y agregue el reductor de forma redux en sus reductores combinados:
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;
Y el módulo validador se ve así:
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;
}
Después de completar el formulario, cuando desee completar todos los campos con algunos valores, puede usar la función de
initialize
:
componentWillMount() {
this.props.dispatch(initialize(''contact'', {
name: ''test''
}, [''name'', ''address'', ''phone'']));
}
Otra forma de llenar los formularios es establecer los valores iniciales.
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 tiene otra forma de manejar esto, ¡solo deje un mensaje! Gracias.
Para aquellos a quienes no les importa una enorme biblioteca para manejar problemas relacionados con formularios, recomendaría redux-form-utils .
Puede generar valor y cambiar manejadores para sus controles de formulario, generar reductores del formulario, creadores de acciones útiles para borrar ciertos (o todos) campos, etc.
Todo lo que necesitas hacer es ensamblarlos en tu código.
Al usar
redux-form-utils
, terminas con una manipulación de formularios como la siguiente:
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>
);
}
}
Sin embargo, esta biblioteca solo resuelve el problema
C
y
U
, para
R
y
D
, quizás un componente de
Table
más integrado sea antipate.
Solo otra cosa para aquellos que desean crear un componente de formulario totalmente controlado sin usar una biblioteca de gran tamaño.
ReduxFormHelper : una pequeña clase ES6, menos de 100 líneas:
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}
}
}
No hace todo el trabajo por ti.
Sin embargo, facilita la creación, validación y manejo de un componente de formulario controlado.
Puede copiar y pegar el código anterior en su proyecto o, en su lugar, incluir la biblioteca respectiva -
redux-form-helper
(plug!).
Cómo utilizar
El primer paso es agregar datos específicos al estado de Redux que representarán el estado de nuestro formulario. Estos datos incluirán valores de campo actuales, así como un conjunto de indicadores de error para cada campo en el formulario.
El estado del formulario puede agregarse a un reductor existente o definirse en un reductor separado.
Además, es necesario definir acciones específicas que inicien la actualización del estado del formulario, así como el creador de acciones respectivo.
Ejemplo de acción :
export const FORM_UPDATE = ''FORM_UPDATE''
export const doFormUpdate = (data, errors) => {
return { type: FORM_UPDATE, data, errors }
}
...
Ejemplo reductor :
...
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 || {})
}
...
}
}
El segundo y último paso es crear un componente contenedor para nuestro formulario y conectarlo con la parte respectiva del estado y las acciones de Redux.
También necesitamos definir un modelo de formulario que especifique la validación de los campos de formulario.
Ahora instanciamos el objeto
ReduxFormHelper
como miembro del componente y pasamos nuestro modelo de formulario y una actualización de envío de devolución de llamada del estado del formulario.
Luego, en el método
render()
del componente, debemos vincular
onChange
cada campo y los eventos
onSubmit
del formulario con los
onSubmit
processField()
y
processForm()
respectivamente, así como mostrar bloques de error para cada campo según los indicadores de error de formulario en el estado.
El siguiente ejemplo utiliza CSS del marco de Twitter Bootstrap.
Ejemplo de componente de contenedor :
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)