2015-10-06 16 views
16

Sono nuovo per reagire e il flusso e sto avendo difficoltà a cercare di capire come caricare i dati da un server. Sono in grado di caricare gli stessi dati da un file locale senza problemi.Come creare chiamate API in REACT e FLUX

Quindi, prima fino ho questo View Controller (Controller-view.js) che passa verso il basso stato iniziale ad una visione (view.js)

controllore 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; 

ho un file azioni che carica il dati INITAL per la mia app. Quindi si tratta di dati che è non chiamato da come azione dell'utente, ma chiamato da getInitialState in vista del regolatore

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; 

E poi il mio API dati assomiglia a questo

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; 

e questo è il negozio 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; 

Così come ho già detto in precedenza, quando carico i dati in locale utilizzando BillApi.getBillLocal() in azioni tutto funziona bene. Ma quando cambio a BillApi.getBillServer() ottengo gli errori followind nella console ...

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 

Ho anche aggiunto un console.log (dati) per BillApi.getBillServer() e posso vedere che il i dati vengono restituiti dal server. Ma viene visualizzato DOPO Ottengo gli avvisi nella console che credo possa essere il problema. Qualcuno può offrire qualche consiglio o aiutarmi a risolverlo? Ci scusiamo per un post così lungo.

UPDATE

ho fatto alcune modifiche al file api.js (controllare qui per il cambiamento e DOM errori plnkr.co/edit/HoXszori3HUAwUOHzPLG), come è stato suggerito che il problema è dovuto a come gestire la promessa. Ma sembra ancora essere lo stesso problema che puoi vedere negli errori DOM.

+0

Cosa stai passando th' subscriptionsList'? Sta cercando 'this.props.subscriptions' ed è inesistente quindi si ottiene' Can not read property 'period' of undefined'. La mia ipotesi è che tu abbia anche qualche tipo di condizione di gara. Il flusso è asincrono per sua natura ... –

+0

Ho pensato che forse era per quello che stavo ottenendo l'errore 'non posso leggere' - a causa delle condizioni della gara. I dati forse non erano ancora stati caricati? Qualche consiglio su come risolverlo? –

+1

Sì, puoi usare il metodo di callback come suggerito da ultralame o puoi dare a '_bill' un oggetto predefinito come' var _bill = {subscriptions: []} 'così quando fai' getInitialState' ottieni il 'bill' tramite 'store.getAllBill()'. quindi quando il componente viene montato, i dati vengono recuperati e lo store emetterà la modifica e aggiornerà il tuo stato –

risposta

8

Questo è un problema asincrono. L'utilizzo di $.getJSON().then() non è sufficiente. Dal momento che restituisce un oggetto promessa, si deve gestire la promessa alla invocazione facendo qualcosa di simile api.getBill().then(function(data) { /*do stuff with data*/ });

ho fatto un CodePen example con il seguente codice:

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

Grazie per la risposta. Ma se controlli api.js dove faccio la chiamata al server con getBillServer puoi vedere che sto usando un .then() e restituisco i dati all'azione. Non è corretto? –

+0

Aggiornato la mia risposta. Fondamentalmente, il primo '.then()' restituisce una promessa, quindi devi usare un altro '.then()' per ottenere i tuoi dati. – ultralame

+0

Grazie mille. Lo sto solo provando ora. Per essere chiari, la funzione searchSpotify in questo caso andrebbe nel mio file api.js e nella chiamata alla funzione (searchSpotify ('donald trump')) nel mio file delle azioni? –

2

Un metodo alternativo sarebbe quello di verificare se l'elica di abbonamento esiste prima di giocare con i dati.

provare a modificare il codice a guardare un po 'come questo:

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

Nella funzione di rendering prima del ritorno provare ad aggiungere il seguente: if (! This.props.subscription = undefined) {// fare qualcosa qui }

Dato che i dati cambiano lo stato del componente di livello superiore, il rendering verrà riattivato una volta che i dati con il sostegno di sottoscrizione sono stati definiti.

+0

Grazie. Dove metterei questo codice?Presumo che non dovrebbe avvolgere la funzione di rendering nella vista del controller? –

+0

Vedere la modifica apportata al codice –

+0

Molte grazie per aver scritto il codice. Posso vedere che è un modo migliore. Ma il problema rimane ancora dove i dati iniziali non vengono caricati. Ricevo ancora l'errore "Attenzione: Impossibile propType: richiesta prop' subscription' non è stata specificata in 'subscriptionsList'. Controlla il metodo render di' viewBill'. " Immagino che lo stato non cambi quando i dati si caricano e il retrigger non sta accadendo. Vedo che i dati sono stati caricati nella finestra della console. Qualche idea su cosa potrebbe causarlo? –

2

se ho capito bene si potrebbe provare con qualcosa di simile

// 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 non restituisce il valore dalla richiesta HTTP. Lo rende disponibile per il callback. La logica alla base di questo è spiegato in dettaglio qui: How to return the response from an asynchronous call?

3

Sembra dal codice che il flusso previsto è qualcosa di simile:

  • alcuni incendi di componenti inizializzare l'azione,
  • azione di inizializzazione chiamate API
  • che attende i risultati dal server (penso qui è dove le cose si guastano: il rendering del componente inizia prima che i risultati del server siano tornati),
  • quindi passa il risultato al negozio,
  • che emette il cambiamento e
  • attiva un nuovo rendering.

In una tipica configurazione di flusso, consiglio strutturare questo alquanto diverso:

  • qualche componente chiama API (ma non viene generato azione al responsabile ancora)
  • API fa getJSON e attende i risultati del server
  • solo dopo aver ricevuto i risultati, API attiva l'azione INITIALIZE con i dati ricevuti
  • negozio risponde all'azione, e si aggiorna con i risultati
  • poi emette il cambiamento
  • che innesca ri-renderizzare

Io non sono così familiarità con jQuery , promesse e concatenamento, ma penso che ciò si traduca approssimativamente nelle seguenti modifiche nel codice:

  • controller-view richiede un listener di modifiche al negozio: aggiungere unFunzioneche aggiunge un listener di eventi alle modifiche di flux store.
  • nella vista controller, il listener di eventi attiva una funzione setState() che recupera la _bill più recente dallo store.
  • sposta il file dispatcher.dispatch() dal file actions.js al file api.js (sostituendo return data);

In questo modo, il componente inizialmente dovrebbe rendere alcuni messaggio 'carico', e aggiornare al più presto i dati da server è in.

2

mi separo le mie azioni, negozi e Vista (Reagire componenti).

Prima di tutto, mi piacerebbe implementare la mia azione come questo:

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

Il prossimo è il mio deposito, quindi posso tenere traccia di tutti i cambiamenti che possono verificarsi improvvisamente durante la mia chiamata 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(); 

Infine, il 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> 
    ); 
    } 

}); 
0

Supponendo che in realtà si sta ricevendo i dati dal vostro API, ma sono sempre troppo tardi e gli errori sono gettati prima, provate questo: nel controller-view.js, aggiungere il seguente:

componentWillMount: function() { 
    BillStore.addChangeListener(this._handleChangedBills); 
}, 

componentWillUnmount: function() { 
    BillStore.removeChangeListener(this._handleChangedBills); 
}, 

_handleChangedBills =() => { 
    this.setState({bill: BillStore.getAllBill()}); 
} 

E nella funzione getInitialState, dare un oggetto vuoto con la struttura che il vostro il codice si aspetta (in particolare, avere un oggetto 'statement' al suo interno). Qualcosa di simile a questo:

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

Quello che sta accadendo è che quando hai trovato il tuo stato iniziale, non è il recupero dal negozio correttamente, e quindi restituirà un oggetto indefinito. Quando richiedi this.state.bill.statement, bill è inizializzato ma non definito, quindi non può trovare nulla chiamato statement, quindi perché devi aggiungerlo. Dopo che il componente ha avuto un po 'più di tempo (questo è un problema asincrono come hanno detto gli altri poster), dovrebbe essere recuperato correttamente dal negozio. Questo è il motivo per cui aspettiamo che il negozio emetta il cambiamento per noi, e quindi prendiamo i dati dal negozio.

Problemi correlati