react example consume reactjs reactjs-flux

reactjs - example - Cómo crear llamadas API en REACT y FLUX



react json api (6)

Soy nuevo en reaccionar y fluir, y estoy teniendo dificultades para tratar de encontrar la manera de cargar datos desde un servidor. Puedo cargar los mismos datos desde un archivo local sin problemas.

Así que primero tengo esta vista de controlador (controller-view.js) que pasa el estado inicial a una vista (view.js)

controller-view.js

var viewBill = React.createClass({ getInitialState: function(){ return { bill: BillStore.getAllBill() }; }, render: function(){ return ( <div> <SubscriptionDetails subscription={this.state.bill.statement} /> </div> ); } }); module.exports = viewBill;

view.js

var subscriptionsList = React.createClass({ propTypes: { subscription: React.PropTypes.array.isRequired }, render: function(){ return ( <div > <h1>Statement</h1> From: {this.props.subscription.period.from} - To {this.props.subscription.period.to} <br /> Due: {this.props.subscription.due}<br /> Issued:{this.props.subscription.generated} </div> ); } }); module.exports = subscriptionsList;

Tengo un archivo de acciones que carga los datos INITOS de mi aplicación. Entonces, estos son datos a los que no se llama acción del usuario, pero se llaman desde getInitialState en la vista del controlador

InitialActions.js

var InitialiseActions = { initApp: function(){ Dispatcher.dispatch({ actionType: ActionTypes.INITIALISE, initialData: { bill: BillApi.getBillLocal() // I switch to getBillServer for date from server } }); } }; module.exports = InitialiseActions;

Y luego mi API de datos se ve así

api.js

var BillApi = { getBillLocal: function() { return billed; }, getBillServer: function() { return $.getJSON(''https://theurl.com/stuff.json'').then(function(data) { return data; }); } }; module.exports = BillApi;

Y esta es la tienda store.js

var _bill = []; var BillStore = assign({}, EventEmitter.prototype, { addChangeListener: function(callback) { this.on(CHANGE_EVENT, callback); }, removeChangeListener: function(callback) { this.removeListener(CHANGE_EVENT, callback); }, emitChange: function() { this.emit(CHANGE_EVENT); }, getAllBill: function() { return _bill; } }); Dispatcher.register(function(action){ switch(action.actionType){ case ActionTypes.INITIALISE: _bill = action.initialData.bill; BillStore.emitChange(); break; default: // do nothing } }); module.exports = BillStore;

Como mencioné anteriormente, cuando cargo datos localmente usando BillApi.getBillLocal () en acciones todo funciona bien. Pero cuando cambio a BillApi.getBillServer () obtengo los siguientes errores en la consola ...

Warning: Failed propType: Required prop `subscription` was not specified in `subscriptionsList`. Check the render method of `viewBill`. Uncaught TypeError: Cannot read property ''period'' of undefined

También agregué un console.log (datos) a BillApi.getBillServer () y puedo ver que los datos son devueltos por el servidor. Pero se muestra DESPUÉS de recibir las advertencias en la consola, que creo que puede ser el problema. ¿Alguien puede ofrecerme algún consejo o ayudarme a solucionarlo? Disculpa por la larga publicación.

ACTUALIZAR

Hice algunos cambios en el archivo api.js (mire aquí para ver los errores DOM y de cambio plnkr.co/edit/HoXszori3HUAwUOHzPLG) ya que se sugirió que el problema se debe a la forma en que manejo la promesa. Pero todavía parece ser el mismo problema que se puede ver en los errores DOM.


Este es un problema asincrónico. Usar $.getJSON().then() no es suficiente. Como devuelve un objeto de promesa, debe manejar la promesa en la invocación haciendo algo como api.getBill().then(function(data) { /*do stuff with data*/ });

Hice un ejemplo de CodePen con el siguiente código:

function searchSpotify(query) { return $.getJSON(''http://ws.spotify.com/search/1/track.json?q='' + query) .then(function(data) { return data.tracks; }); } searchSpotify(''donald trump'') .then(function(tracks) { tracks.forEach(function(track) { console.log(track.name); }); });


Un método alternativo sería verificar si existe el soporte de la suscripción antes de jugar con los datos.

Intenta modificar tu código para que se vea así:

render: function(){ var subscriptionPeriod = ''''; var subscriptionDue = ['''','''']; var subscriptionGenerated = ''''; if(this.props.subscription !== undefined){ subscriptionPeriod = this.props.subscription.period; subscriptionDue = [this.props.subscription.due.to,this.props.subscription.due.from]; subscriptionGenerated = this.props.subscription.generated; } return ( <div > <h1>Statement</h1> From: {subscriptionPeriod[0]} - To {subscriptionPeriod[1]} <br /> Due: {subscriptionDue}<br /> Issued:{subscriptionGenerated} </div> ); }

En la función de renderizado antes de la devolución intente agregar lo siguiente: if (this.props.subscription! = Undefined) {// hacer algo aquí}

Debido a que sus datos cambian el estado del componente de nivel superior, volverá a disparar el procesamiento una vez que tenga los datos con el elemento de suscripción definido.


Parece que desde su código el flujo previsto es algo así como:

  • algunos incendios de componentes inician la acción,
  • inicializar llamadas de acción API
  • que espera los resultados del servidor (creo que aquí es donde las cosas se descomponen: su componente se inicia antes de que los resultados del servidor estén de vuelta),
  • luego pasa el resultado a la tienda,
  • que emite cambios y
  • desencadena una nueva representación.

En una configuración de flujo típica, aconsejaría estructurar esto algo diferente:

  • algunas llamadas a componentes API ( pero aún no activan acciones para el despachador )
  • La API getJSON y espera los resultados del servidor
  • solo después de que se reciben los resultados , la API activa la acción INICIALIZAR con los datos recibidos
  • la tienda responde a la acción y se actualiza con resultados
  • luego emite cambio
  • que desencadena re-render

No estoy tan familiarizado con jquery, las promesas y el encadenamiento, pero creo que esto se traduciría más o menos en los siguientes cambios en tu código:

  • la vista de controlador necesita un detector de cambios en la tienda: agregue una función componentDidMount() que agrega un detector de eventos a los cambios de la tienda de flujo.
  • en la vista de controlador, el detector de eventos desencadena una función setState() , que recupera la última _bill de la tienda.
  • mueva el dispatcher.dispatch() de su actions.js a su api.js (reemplazando return data );

De esta forma, su componente inicialmente debe mostrar un mensaje de "carga" y actualizarse tan pronto como se encuentren los datos del servidor.


Voy a separar mis acciones, tiendas y vistas (componentes Reaccionar).

Antes que nada, implementaría mi Acción de esta manera:

import keyMirror from ''keymirror''; import ApiService from ''../../lib/api''; import Dispatcher from ''../dispatcher/dispatcher''; import config from ''../env/config''; export let ActionTypes = keyMirror({ GetAllBillPending: null, GetAllBillSuccess: null, GetAllBillError: null }, ''Bill:''); export default { fetchBills () { Dispatcher.dispatch(ActionTypes.GetAllBillPending); YOUR_API_CALL .then(response => { //fetchs your API/service call to fetch all Bills Dispatcher.dispatch(ActionTypes.GetAllBillSuccess, response); }) .catch(err => { //catches error if you want to Dispatcher.dispatch(ActionTypes.GetAllBillError, err); }); } };

El siguiente es mi tienda, así que puedo hacer un seguimiento de todos los cambios que de repente pueden ocurrir durante mi llamada de API:

class BillStore extends YourCustomStore { constructor() { super(); this.bindActions( ActionTypes.GetAllBillPending, this.onGetAllBillPending, ActionTypes.GetAllBillSuccess, this.onGetAllBillSuccess, ActionTypes.GetAllBillError , this.onGetAllBillError ); } getInitialState () { return { bills : [] status: Status.Pending }; } onGetAllBillPending () { this.setState({ bills : [] status: Status.Pending }); } onGetAllBillSuccess (payload) { this.setState({ bills : payload status: Status.Ok }); } onGetAllBillError (error) { this.setState({ bills : [], status: Status.Errors }); } } export default new BillStore();

Finalmente, tu componente:

import React from ''react''; import BillStore from ''../stores/bill''; import BillActions from ''../actions/bill''; export default React.createClass({ statics: { storeListeners: { ''onBillStoreChange'': BillStore }, }, getInitialState () { return BillStore.getInitialState(); }, onBillStoreChange () { const state = BillStore.getState(); this.setState({ bills : state.bills, pending: state.status === Status.Pending }); }, componentDidMount () { BillActions.fetchBills(); }, render () { if (this.state.pending) { return ( <div> {/* your loader, or pending structure */} </div> ); } return ( <div> {/* your Bills */} </div> ); } });


Suponiendo que en realidad está obteniendo los datos de su API, pero lo está haciendo demasiado tarde y los errores se producen primero, intente esto: en su controlador-view.js, agregue lo siguiente:

componentWillMount: function () { BillStore.addChangeListener(this._handleChangedBills); }, componentWillUnmount: function () { BillStore.removeChangeListener(this._handleChangedBills); }, _handleChangedBills = () => { this.setState({bill: BillStore.getAllBill()}); }

Y en su función getInitialState, proporcione un objeto vacío con la estructura que su código espera (específicamente, tiene un objeto ''statement'' dentro de él). Algo como esto:

getInitialState: function(){ return { bill: { statement: [] } }; },

Lo que está sucediendo es que cuando obtiene su estado inicial, no está obteniendo de la tienda de forma adecuada, por lo que devolverá un objeto indefinido. Cuando luego solicita esta declaración state.bill.statement, la factura se inicializa pero no está definida, por lo que no puede encontrar nada llamado statement, por lo que debe agregarlo. Después de que el componente haya tenido un poco más de tiempo (esto es un problema asincrónico como los otros carteles), debe buscarse apropiadamente en la tienda. Es por eso que esperamos que la tienda emita el cambio para nosotros, y luego tomamos los datos de la tienda.


Si entiendo correctamente, podrías intentar con algo como esto

// InitialActions.js var InitialiseActions = { initApp: function(){ BillApi.getBill(function(result){ // result from getJson is available here Dispatcher.dispatch({ actionType: ActionTypes.INITIALISE, initialData: { bill: result } }); }); } }; module.exports = InitialiseActions; //api.js var BillApi = { getBillLocal: function() { console.log(biller); return biller; }, getBill: function(callback) { $.getJSON(''https://theurl.com/stuff.json'', callback); } };

$ .getJSON no devuelve el valor de la solicitud http. Lo hace disponible para la devolución de llamada. La lógica detrás de esto se explica en detalle aquí: ¿Cómo devolver la respuesta de una llamada asíncrona?