2015-12-14 15 views
14

Sulle pagine ufficiali e nelle questioni GitHub per redux-form ci sono più di un esempio di come lavorare con initialValues ​​ però non riesco a trovare un un singolo che si concentra sulla spiegazione di come initialValues ​​ può essere impostato in risposta a una sorgente asincrona.Come impostare initialValues ​​basate su fonte asincrona, come una chiamata AJAX con Redux-forma

Il caso principale che ho in mente è qualcosa come una semplice applicazione CRUD in cui un utente sta per modificare alcune entità già esistenti. Quando la vista viene aperta per la prima volta e il componente redux-form viene montato ma prima che il componente venga reso, i valori iniziali devono essere impostati. Diciamo che in questo esempio i dati vengono caricati su richiesta quando il componente viene prima montato e sottoposto a rendering per la prima volta. Gli esempi mostrano l'impostazione di valori iniziali basati su valori codificati o sullo stato del redux store, ma nessuno dei quali riesco a trovare si concentra su come impostare i valori iniziali in base a qualcosa di asincrono come una chiamata a XHR o recupero.

Sono sicuro che mi manca solo qualcosa di fondamentale quindi per favore indicami la giusta direzione.

Riferimenti:

risposta

2

Potrebbe sparare il messaggio concernente componentWillMount(), e impostare lo stato del caricamento.

Durante il caricamento, eseguire il rendering di uno spinner ad esempio e solo quando la richiesta restituisce i valori, aggiornare lo stato e quindi eseguire nuovamente il rendering del modulo con i valori ??

8

EDIT:. Aggiornato Soluzione da docs ReduxForm

Questo è ora [documentato] ((http://redux-form.com/6.0.0-alpha.4/examples/initializeFromState/) nella sua ultima versione di ReduxForm, ed è molto più semplice di quanto la mia risposta precedente

La chiave è per connect il componente modulo dopo decorare con ReduxForm. Allora si sarà in grado di accedere al puntello initialValues come qualsiasi altro puntello sul 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) 

Ho realizzato questo utilizzando il metodo redux-form reducer plugin.

Le demo seguenti recuperano dati asincroni e precompilano un modulo utente con risposta.

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

Poi, nel tuo riduttore principale in cui si include il tuo redux-form riduttore è possibile includere il plug-riduttore che sostituisce i valori moduli con i dati recuperati restituiti.

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

Infine, si include la combinazione del nuovo formReducer con gli altri riduttori nella propria app.

Nota Quanto segue presuppone che le chiavi dell'oggetto utente recuperato corrispondano ai nomi dei campi nel modulo utente. In caso contrario, sarà necessario eseguire un ulteriore passaggio sui dati per mappare i campi.

+0

Questo ha funzionato per me, anche se sembra come un sacco di testo standard per un funzionamento di base. Si noti che la forma dello stato restituito dal riduttore del plugin (contenente un oggetto 'values') è importante. – ChidG

+1

@ChidG Ho aggiornato la mia risposta con una soluzione aggiornata che ho trovato recentemente negli ultimi documenti redux-form. Molto meno piastra di caldaia, e waaaay più dritto in avanti. – ryandrewjohnson

+0

Grazie - Ho provato quel metodo documentato, ma non ho potuto farlo funzionare. Per ora rimarrò con la soluzione 'formReducer.plugin'. – ChidG

3

Per impostazione predefinita, è possibile inizializzare un componente del modulo solo una volta tramite InitialValues. Esistono due metodi per reinizializzare il componente del modulo con i nuovi valori "pristine":

Passare un parametro di configurazione enableReinitialize prop o reduxForm() impostato su true per consentire al modulo di reinizializzare con nuovi valori "pristine" ogni volta che i valori iniziali puntano i cambiamenti. Per mantenere i valori del modulo sporco quando reinizializza, puoi impostare keepDirtyOnReinitialize su true. Per impostazione predefinita, la reinizializzazione del modulo sostituisce tutti i valori dirty con valori "pristine".

Invia l'azione INITIALIZE (utilizzando l'action creator fornito da redux-form).

di riferimento da: http://redux-form.com/6.1.1/examples/initializeFromState/

0

Anche se questo metodo non può essere la soluzione migliore, funziona abbastanza bene per le mie esigenze:

  • richiesta AJAX API in entrata
  • Inizializza form con i dati quando richiesta è stata soddisfatta o viene visualizzato un errore del server
  • Il ripristino del modulo verrà comunque reimpostato sui dati iniziali iniziali
  • Consente al modulo di essere reu sed per altri scopi (ad esempio, un'istruzione if semplice potrebbe ignorare l'impostazione dei valori iniziali): Aggiungi post e modifica post o aggiungi commento e modifica commento ... ecc.
  • dati vengono rimossi dal modulo Redux in uscita (nessuna ragione per memorizzare i nuovi dati in Redux dal momento che è in fase di ri-reso da un componente Blog)

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;