react form example español ajax asynchronous redux redux-form

ajax - español - redux form example



Cómo configurar valores iniciales en función de una fuente asíncrona, como una llamada ajax con redux-form (5)

¿Podría desencadenar el envío en componentWillMount () y establecer el estado en carga?

Mientras se está cargando, haga un giro por ejemplo, y solo cuando la solicitud regrese con los valores, actualice el estado y luego vuelva a renderizar el formulario con los valores.

En las páginas oficiales y en los problemas de GitHub para redux-form hay más de un ejemplo de cómo trabajar con valores iniciales. Sin embargo, no puedo encontrar uno que se centre en explicar cómo se pueden establecer valores iniciales en respuesta a una fuente asíncrona.

El caso principal que tengo en mente es algo así como una simple aplicación CRUD donde un usuario editará una entidad que ya existe. Cuando la vista se abre por primera vez y el componente redux-form se monta pero antes de renderizar el componente, se deben establecer los valores iniciales. Digamos que en este ejemplo, los datos se cargan a pedido cuando el componente se monta por primera vez y se procesa por primera vez. Los ejemplos muestran la configuración de valores iniciales basados ​​en valores codificados o el estado de almacenamiento de redux, pero ninguno que pueda encontrar se centra en cómo establecer valores iniciales basados ​​en algo asíncrono como una llamada a XHR o búsqueda.

Estoy seguro de que me estoy perdiendo algo fundamental, así que, por favor, indícame la dirección correcta.

Referencias:


De forma predeterminada, solo puede inicializar un componente de formulario una vez a través de initialValues. Hay dos métodos para reinicializar el componente de formulario con nuevos valores "prístinos":

Pase un parámetro de configuración enableReinitialize prop o reduxForm () a true para permitir que el formulario se reinicialice con nuevos valores "pristine" cada vez que cambie el valor inicial initial. Para mantener los valores de forma sucia cuando se reinicializa, puede establecer keepDirtyOnReinitialize en true. De forma predeterminada, al reinicializar el formulario se reemplazan todos los valores sucios con valores "prístinos".

Envíe la acción INICIALIZAR (utilizando el creador de acción proporcionado por redux-form).

Referenciado en: http://redux-form.com/6.1.1/examples/initializeFromState/


Este es un ejemplo de trabajo mínimo sobre cómo establecer valores iniciales en función de una fuente asíncrona.
Utiliza el creador de acciones initialize .

Todos los valores de initialValues no deben estar indefinidos, o obtendrás un bucle infinito .

// import { Field, reduxForm, change, initialize } from ''redux-form''; async someAsyncMethod() { // fetch data from server await this.props.getProducts(), // this allows to get current values of props after promises and benefits code readability const { products } = this.props; const initialValues = { productsField: products }; // set values as pristine to be able to detect changes this.props.dispatch(initialize( ''myForm'', initialValues, )); }


Si bien este método puede no ser la mejor solución, funciona lo suficientemente bien para mis necesidades:

  • Solicitud de AJAX a API en entrada
  • Inicializa el formulario con datos cuando la solicitud se ha cumplido o muestra un error del servidor
  • Restablecer el formulario todavía se restablecerá a los datos iniciales iniciales
  • Permite que el formulario se reutilice para otros fines (por ejemplo, una declaración simple si podría omitir la configuración de valores iniciales): Agregar publicación y Editar publicación o Agregar comentario y Editar comentario ... etc.
  • Los datos se eliminan del formulario de Redux en la salida (no hay razón para almacenar nuevos datos en Redux, ya que un componente de Blog los vuelve a representar)

Form.jsx:

import React, { Component } from ''react''; import { Field, reduxForm } from ''redux-form''; import { connect } from ''react-redux''; import { browserHistory, Link } from ''react-router''; import { editPost, fetchPost } from ''../../actions/BlogActions.jsx''; import NotFound from ''../../components/presentational/notfound/NotFound.jsx''; import RenderAlert from ''../../components/presentational/app/RenderAlert.jsx''; import Spinner from ''../../components/presentational/loaders/Spinner.jsx''; // form validation checks const validate = (values) => { const errors = {} if (!values.title) { errors.title = ''Required''; } if (!values.image) { errors.image = ''Required''; } if (!values.description) { errors.description = ''Required''; } else if (values.description.length > 10000) { errors.description = ''Error! Must be 10,000 characters or less!''; } return errors; } // renders input fields const renderInputField = ({ input, label, type, meta: { touched, error } }) => ( <div> <label>{label}</label> <div> <input {...input} className="form-details complete-expand" placeholder={label} type={type}/> {touched && error && <div className="error-handlers "><i className="fa fa-exclamation-triangle" aria-hidden="true"></i> {error}</div>} </div> </div> ) // renders a text area field const renderAreaField = ({ textarea, input, label, type, meta: { touched, error } }) => ( <div> <label>{label}</label> <div> <textarea {...input} className="form-details complete-expand" placeholder={label} type={type}/> {touched && error && <div className="error-handlers"><i className="fa fa-exclamation-triangle" aria-hidden="true"></i> {error}</div>} </div> </div> ) class BlogPostForm extends Component { constructor() { super(); this.state = { isLoaded: false, requestTimeout: false, }; } componentDidMount() { if (this.props.location.query.postId) { // sets a 5 second server timeout this.timeout = setInterval(this.timer.bind(this), 5000); // AJAX request to API fetchPost(this.props.location.query.postId).then((res) => { // if data returned, seed Redux form if (res.foundPost) this.initializeForm(res.foundPost); // if data present, set isLoaded to true, otherwise set a server error this.setState({ isLoaded: (res.foundPost) ? true : false, serverError: (res.err) ? res.err : '''' }); }); } } componentWillUnmount() { this.clearTimeout(); } timer() { this.setState({ requestTimeout: true }); this.clearTimeout(); } clearTimeout() { clearInterval(this.timeout); } // initialize Redux form from API supplied data initializeForm(foundPost) { const initData = { id: foundPost._id, title: foundPost.title, image: foundPost.image, imgtitle: foundPost.imgtitle, description: foundPost.description } this.props.initialize(initData); } // onSubmit => take Redux form props and send back to server handleFormSubmit(formProps) { editPost(formProps).then((res) => { if (res.err) { this.setState({ serverError: res.err }); } else { browserHistory.push(/blog); } }); } renderServerError() { const { serverError } = this.state; // if form submission returns a server error, display the error if (serverError) return <RenderAlert errorMessage={serverError} /> } render() { const { handleSubmit, pristine, reset, submitting, fields: { title, image, imgtitle, description } } = this.props; const { isLoaded, requestTimeout, serverError } = this.state; // if data hasn''t returned from AJAX request, then render a spinner if (this.props.location.query.postId && !isLoaded) { // if AJAX request returns an error or request has timed out, show NotFound component if (serverError || requestTimeout) return <NotFound /> return <Spinner /> } // if above conditions are met, clear the timeout, otherwise it''ll cause the component to re-render on timer''s setState function this.clearTimeout(); return ( <div className="col-sm-12"> <div className="form-container"> <h1>Edit Form</h1> <hr /> <form onSubmit={handleSubmit(this.handleFormSubmit.bind(this))}> <Field name="title" type="text" component={renderInputField} label="Post Title" /> <Field name="image" type="text" component={renderInputField} label="Image URL" /> <Field name="imgtitle" component={renderInputField} label="Image Description" /> <Field name="description" component={renderAreaField} label="Description" /> <div> <button type="submit" className="btn btn-primary partial-expand rounded" disabled={submitting}>Submit</button> <button type="button" className="btn btn-danger partial-expand rounded f-r" disabled={ pristine || submitting } onClick={ reset }>Clear Values</button> </div> </form> { this.renderServerError() } </div> </div> ) } } BlogPostForm = reduxForm({ form: ''BlogPostForm'', validate, fields: [''name'', ''image'', ''imgtitle'', ''description''] })(BlogPostForm); export default BlogPostForm = connect(BlogPostForm);

BlogActions.jsx:

import * as app from ''axios''; const ROOT_URL = ''http://localhost:3001''; // submits Redux form data to server export const editPost = ({ id, title, image, imgtitle, description, navTitle }) => { return app.put(`${ROOT_URL}/post/edit/${id}?userId=${config.user}`, { id, title, image, imgtitle, description, navTitle }, config) .then(response => { return { success: response.data.message } }) .catch(({ response }) => { if(response.data.deniedAccess) { return { err: response.data.deniedAccess } } else { return { err: response.data.err } } }); } // fetches a single post from the server for front-end editing export const fetchPost = (id) => { return app.get(`${ROOT_URL}/posts/${id}`) .then(response => { return { foundPost: response.data.post} }) .catch(({ response }) => { return { err: response.data.err }; }); }

RenderAlert.jsx:

import React, { Component } from ''react''; const RenderAlert = (props) => { const displayMessage = () => { const { errorMessage } = props; if (errorMessage) { return ( <div className="callout-alert"> <p> <i className="fa fa-exclamation-triangle" aria-hidden="true"/> <strong>Error! </strong> { errorMessage } </p> </div> ); } } return ( <div> { displayMessage() } </div> ); } export default RenderAlert;

Reducers.jsx

import { routerReducer as routing } from ''react-router-redux''; import { reducer as formReducer } from ''redux-form''; import { combineReducers } from ''redux''; const rootReducer = combineReducers({ form: formReducer, routing }); export default rootReducer;


EDITAR: Solución actualizada de los documentos de ReduxForm

Esto ahora está documented en la última versión de ReduxForm, y es mucho más simple que mi respuesta anterior.

La clave es connect su componente de formulario después de decorarlo con ReduxForm. Entonces podrá acceder a la propiedad initialValues como cualquier otra parte de su componente.

// Decorate with reduxForm(). It will read the initialValues prop provided by connect() InitializeFromStateForm = reduxForm({ form: ''initializeFromState'' })(InitializeFromStateForm) // now set initialValues using data from your store state InitializeFromStateForm = connect( state => ({ initialValues: state.account.data }) )(InitializeFromStateForm)

Logré esto usando el método del complemento reductor de forma redux.

Las siguientes demostraciones recogen datos asíncronos y rellenan previamente un formulario de usuario con respuesta.

const RECEIVE_USER = ''RECEIVE_USER''; // once you''ve received data from api dispatch action const receiveUser = (user) => { return { type: RECEIVE_USER, payload: { user } } } // here is your async request to retrieve user data const fetchUser = (id) => dispatch => { return fetch(''http://getuser.api'') .then(response => response.json()) .then(json => receiveUser(json)); }

Luego, en su reductor de raíz, donde incluye su reductor de redux-form , incluiría su complemento reductor que anula los valores de los formularios con los datos recuperados.

const formPluginReducer = { form: formReducer.plugin({ // this would be the name of the form you''re trying to populate user: (state, action) => { switch (action.type) { case RECEIVE_USER: return { ...state, values: { ...state.values, ...action.payload.user } } default: return state; } } }) }; const rootReducer = combineReducers({ ...formPluginReducer, ...yourOtherReducers });

Finalmente, incluye que combina su nuevo formReducer con los otros reductores en su aplicación.

Nota Lo siguiente asume que las claves del objeto de usuario obtenido coinciden con los nombres de los campos en el formulario de usuario. Si este no es el caso, deberá realizar un paso adicional en los datos para asignar campos.