We have just started up a new app and we are using React with the Flux architecture. We have done our best to piece together what we think is the best structure for the app and would appreciate any feedback, suggestions etc. The component is essentially a settings page in a much larger system, it basically gets and updates shop details which consist of a name, URL and country code. The other settings pages will have a similar functionality and wanted to make sure that this was a sound approach before we get going with the rest.
Component
var React = require('react');
var ShopDetailsStore = require('../Stores/ShopDetailsStore.js');
var Dispatcher = require('../../../../../Dispatchers/AppDispatcher.js');
var Actions = require("../Actions/ShopDetailsActions.js");
function getState(){
return {
shopDetails: ShopDetailsStore.getShopDetails(),
errors: ShopDetailsStore.getErrors()
};
}
var ShopDetails = React.createClass({
getInitialState:function(){
return getState();
},
componentDidMount: function (){
Actions.getShopDetails();
ShopDetailsStore.addChangeListener(this._onChange);
},
componentWillMount: function(){
ShopDetailsStore.removeChangeListener(this._onChange);
},
updateShopDetails:function(e){
var shopName = this.state.shopDetails.ShopName;
var siteURL = this.state.shopDetails.SiteURL;
var countryCode = this.state.shopDetails.CountryCode;
Actions.putShopDetails({ShopName: shopName, SiteURL: siteURL, CountryCode: countryCode});
},
handleChangeShopName:function(e){
this.setState({shopDetails: {ShopName: e.target.value, SiteURL: this.state.shopDetails.SiteURL, CountryCode: this.state.shopDetails.CountryCode}});
},
handleChangeSiteURL:function(e){
this.setState({shopDetails: {ShopName: this.state.shopDetails.ShopName, SiteURL: e.target.value, CountryCode: this.state.shopDetails.CountryCode}});
},
handleChangeTradingCountry:function(e){
this.setState({shopDetails: {ShopName: this.state.shopDetails.ShopName, SiteURL: this.state.shopDetails.SiteURL, CountryCode: e.target.value}});
},
render: function(){
var errors = this.state.errors.map(function(error){
return (<span className="error" key={error.readyState}>{error.status} : {error.statusText}</span>);
});
return (<div>
<div>{errors}</div>
<input type="text" value={this.state.shopDetails.ShopName} onChange={this.handleChangeShopName}/>
<input type="text" value={this.state.shopDetails.SiteURL} onChange={this.handleChangeSiteURL}/>
<select value={this.state.shopDetails.CountryCode} onChange={this.handleChangeTradingCountry}>
<option value="GB">United Kingdom</option>
<option value="US">United States</option>
</select>
<input type="button" onClick={this.updateShopDetails} value="Update Details"/>
</div>);
},
_onChange: function(){
this.setState(getState());
}
});
module.exports = ShopDetails;
Actions - You'll notice we are doing some weird stuff in the success callback of the putShopDetails
request, basically the api returns a success even if it doesn't update anything so it's just a fudge to cover that scenario.
var ShopDetailsConstants = require('../Constants/ShopDetailsConstants.js');
var AppDispatcher = require('../../../../../Dispatchers/AppDispatcher.js');
var $ = require('jquery');
var ShopDetailsActions = {
putShopDetails:function(shopDetails){
$.ajax({
url: "/api/settings/shopdetails",
type: 'PUT',
data: shopDetails,
success: function(data){
if(data.RowsAffected <= 0){
AppDispatcher.dispatch({
actionType: ShopDetailsConstants.PUT_SHOPDETAILS_FAILURE,
shopDetails: shopDetails,
error: { readyState: 1, statusText: data.Status.StatusCode, status: 500 }
});
} else {
AppDispatcher.dispatch({
actionType: ShopDetailsConstants.PUT_SHOPDETAILS_SUCCESS,
shopDetails: shopDetails
});
}
},
error: function(error){
AppDispatcher.dispatch({
actionType: ShopDetailsConstants.PUT_SHOPDETAILS_FAILURE,
error: error,
shopDetails: shopDetails
});
}
});
},
getShopDetails:function(){
$.ajax({
url: "/api/settings/shopdetails",
type: 'GET',
success: function(data){
AppDispatcher.dispatch({
actionType: ShopDetailsConstants.GET_SHOPDETAILS_SUCCESS,
shopDetails: data
});
},
error: function(error){
AppDispatcher.dispatch({
actionType: ShopDetailsConstants.GET_SHOPDETAILS_FAILURE,
error: error
});
}
});
}
};
module.exports = ShopDetailsActions;
Store
var AppDispatcher = require('../../../../../Dispatchers/AppDispatcher.js');
var ShopDetailsConstants = require('../Constants/ShopDetailsConstants.js');
var EventEmitter = require('events').EventEmitter;
var assign = require('object-assign');
var CHANGE_EVENT = "change";
var _shopDetails = {};
var _errors = [];
var ShopDetailsStore = assign({}, EventEmitter.prototype, {
emitChange:function(){
this.emit(CHANGE_EVENT)
},
addChangeListener:function(callback){
this.on(CHANGE_EVENT, callback)
},
removeChangeListener:function(callback){
this.removeListener(CHANGE_EVENT, callback)
},
getShopDetails:function(){
return _shopDetails;
},
getErrors:function(){
return _errors;
}
});
AppDispatcher.register(function(action){
_errors = [];
switch(action.actionType){
case ShopDetailsConstants.GET_SHOPDETAILS_SUCCESS:
_shopDetails = action.shopDetails;
break;
case ShopDetailsConstants.GET_SHOPDETAILS_FAILURE:
_shopDetails = action.shopDetails;
_errors.push(action.error);
break;
case ShopDetailsConstants.PUT_SHOPDETAILS_SUCCESS:
_shopDetails = action.shopDetails;
break;
case ShopDetailsConstants.PUT_SHOPDETAILS_FAILURE:
_shopDetails = action.shopDetails;
_errors.push(action.error);
break;
default:
}
ShopDetailsStore.emitChange();
});
module.exports = ShopDetailsStore;