I'm new to react and flux and I am having a hard time trying to figure out how to load data from a server. I am able to load the same data from a local file with no issues.
So first up I have this controller view (controller-view.js) that passes down initial state to a view (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;
I have an actions file that loads the INITAL data for my app. So this is data that is not called by as user action, but called from getInitialState in the controller view
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;
And then my data API looks like this
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;
And this is the store 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;
So as I mentioned earlier, when I load data locally using BillApi.getBillLocal() in actions everything works fine. But when I change to BillApi.getBillServer() I get the followind errors in the 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
I also added a console.log(data) to BillApi.getBillServer() and I can see that the data is returned from the server. But it is displayed AFTER I get the warnings in the console which I believe may be the issue. Can anyone offer some advice or help me to fix it? Sorry for such a long post.
UPDATE
I made some changes to the api.js file (check here for change and DOM errors plnkr.co/edit/HoXszori3HUAwUOHzPLG ) as it was suggested that the issue is due to how I handle the promise. But it still seems to be the same issue as you can see in the DOM errors.
Go to the appDispatcher. js and copy-paste the following code: import { Dispatcher } from "flux"; const dispatcher = new Dispatcher(); export default dispatcher; Here we are importing the Dispatcher from the flux library that we installed, creating a new object and exporting it so that our actions module can use it.
Flux is a pattern for managing how data flows through a React application. As we've seen, the preferred method of working with React components is through passing data from one parent component to it's children components. The Flux pattern makes this model the default method for handling data.
Summary: Calling API in constructor() or componentWillMount() is not a syntax error but increases code complexity and hampers performance. So, to avoid unnecessary re-rendering and code complexity, it's better to call API after render(), i.e componentDidMount().
This is an async issue. Using $.getJSON().then()
is not enough. Since it returns a promise object, you have to handle the promise at invocation by doing something like api.getBill().then(function(data) { /*do stuff with data*/ });
I made a CodePen example with the following code:
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);
});
});
It looks like from your code that the intended flow is something like:
In a typical flux setup, I would advise to structure this somewhat different:
getJSON
and waits for server resultsI am not so familiar with jquery, promises and chaining, but I think this would roughly translate into the following changes in your code:
componentDidMount()
function that adds an event listener to flux store changes. setState()
function, which fetches the most recent _bill from the store.dispatcher.dispatch()
from your actions.js to your api.js (replacing return data
);That way, your component initially should render some 'loading' message, and update as soon as data from server is in.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With