Where should ajax request be made in Flux app?
I have been using Binary Muse's example from the Fluxxor ajax example. Here is my very simple example using the same approach.
I have a simple product store some product actions and the controller-view component which has sub-components that all respond to changes made to the product store. For instance product-slider, product-list and product-search components.
Fake Product Client
Here is the fake client which you could substitute for calling an actual endpoint returning products.
var ProductClient = {
load: function(success, failure) {
setTimeout(function() {
var ITEMS = require('../data/product-data.js');
success(ITEMS);
}, 1000);
}
};
module.exports = ProductClient;
Product Store
Here is the Product Store, obviously this is a very minimal store.
var Fluxxor = require("fluxxor");
var store = Fluxxor.createStore({
initialize: function(options) {
this.productItems = [];
this.bindActions(
constants.LOAD_PRODUCTS_SUCCESS, this.onLoadSuccess,
constants.LOAD_PRODUCTS_FAIL, this.onLoadFail
);
},
onLoadSuccess: function(data) {
for(var i = 0; i < data.products.length; i++){
this.productItems.push(data.products[i]);
}
this.emit("change");
},
onLoadFail: function(error) {
console.log(error);
this.emit("change");
},
getState: function() {
return {
productItems: this.productItems
};
}
});
module.exports = store;
Now the product actions, which make the AJAX request and on success fire the LOAD_PRODUCTS_SUCCESS action returning products to the store.
Product Actions
var ProductClient = require("../fake-clients/product-client");
var actions = {
loadProducts: function() {
ProductClient.load(function(products) {
this.dispatch(constants.LOAD_PRODUCTS_SUCCESS, {products: products});
}.bind(this), function(error) {
this.dispatch(constants.LOAD_PRODUCTS_FAIL, {error: error});
}.bind(this));
}
};
module.exports = actions;
So calling this.getFlux().actions.productActions.loadProducts()
from any component listening to this store would load the products.
You could imagine having different actions though which would respond to user interactions like addProduct(id)
removeProduct(id)
etc... following the same pattern.
Hope that example helps a bit, as I found this a little tricky to implement, but certainly helped in keeping my stores 100% synchronous.
Fluxxor has an example of async communication with an API.
This blog post has talks about it and has been featured on React's blog.
I find this a very important and difficult question that is not clearly answered yet, as frontend software synchronization with the backend is still a pain.
Should API requests be made in JSX components? Stores? Other place?
Performing requests in stores mean that if 2 stores need the same data for a given action, they will issue 2 similar requets (unless you introduce dependencies between stores, which I really don't like)
In my case, I have found this very handy to put Q promises as payload of actions because:
- My actions do not need to be serializable (I do not keep an event log, I don't need event replay feature of event sourcing)
- It removes the need to have different actions/events (request fired/request completed/request failed) and have to match them using correlation ids when concurrent requests can be fired.
- It permits to multiple store to listen to the completion of the same request, without introducing any dependency between the stores (however it may be better to introduce a caching layer?)
Ajax is EVIL
I think Ajax will be less and less used in the near future because it is very hard to reason about. The right way? Considering devices as part of the distributed system I don't know where I first came across this idea (maybe in this inspiring Chris Granger video).
Think about it. Now for scalability we use distributed systems with eventual consistency as storage engines (because we can't beat the CAP theorem and often we want to be available). These systems do not sync through polling each others (except maybe for consensus operations?) but rather use structures like CRDT and event logs to make all the members of the distributed system eventually consistent (members will converge to the same data, given enough time).
Now think about what is a mobile device or a browser. It is just a member of the distributed system that may suffer of network latency and network partitionning. (ie you are using your smartphone on the subway)
If we can build network partition and network speed tolerant databases (I mean we can still perform write operations to an isolated node), we can probably build frontend softwares (mobile or desktop) inspired by these concepts, that work well with offline mode supported out of the box without app features unavailability.
I think we should really inspire ourselves of how databases are working to architecture our frontend applications. One thing to notice is that these apps do not perform POST and PUT and GET ajax requests to send data to each others, but rather use event logs and CRDT to ensure eventual consistency.
So why not do that on the frontend? Notice that the backend is already moving in that direction, with tools like Kafka massively adopted by big players. This is somehow related to Event Sourcing / CQRS / DDD too.
Check these awesome articles from Kafka authors to convince yourself:
- STREAM PROCESSING, EVENT SOURCING, REACTIVE, CEP… AND MAKING SENSE OF IT ALL
- The Log: What every software engineer should know about real-time data's unifying abstraction.
Maybe we can start by sending commands to the server, and receiving a stream of server events (through websockets for exemple), instead of firing Ajax requests.
I have never been very comfortable with Ajax requests. As we React developpers tend to be functional programmers. I think it's hard to reason about local data that is supposed to be your "source of truth" of your frontend application, while the real source of truth is actually on the server database, and your "local" source of truth may already be outdated when you receive it, and will never converge to the real source of truth value unless you press some lame Refresh button... Is this engineering?
However it's still a bit hard to design such a thing for some obvious reasons:
- Your mobile/browser client has limited resources and can not necessarily store all the data locally (thus sometimes requiring polling with an ajax request heavy content)
- Your client should not see all the data of the distributed system so it requires somehow to filter the events that it receives for security reasons
You can call for data in either the action creators or the stores. The important thing is to not handle the response directly, but to create an action in the error/success callback. Handling the response directly in the store leads to a more brittle design.
I'm a big proponent of putting async write operations in the action creators and async read operations in the store. The goal is to keep the store state modification code in fully synchronous action handlers; this makes them simple to reason about and simple to unit test. In order to prevent multiple simultaneous requests to the same endpoint (for example, double-reading), I'll move the actual request processing into a separate module that uses promises to prevent the multiple requests; for example:
class MyResourceDAO {
get(id) {
if (!this.promises[id]) {
this.promises[id] = new Promise((resolve, reject) => {
// ajax handling here...
});
}
return this.promises[id];
}
}
While reads in the store involve asynchronous functions, there is an important caveat that the stores don't update themselves in the async handlers, but instead fire an action and only fire an action when the response arrives. Handlers for this action end up doing the actual state modification.
For example, a component might do:
getInitialState() {
return { data: myStore.getSomeData(this.props.id) };
}
The store would have a method implemented, perhaps, something like this:
class Store {
getSomeData(id) {
if (!this.cache[id]) {
MyResurceDAO.get(id).then(this.updateFromServer);
this.cache[id] = LOADING_TOKEN;
// LOADING_TOKEN is a unique value of some kind
// that the component can use to know that the
// value is not yet available.
}
return this.cache[id];
}
updateFromServer(response) {
fluxDispatcher.dispatch({
type: "DATA_FROM_SERVER",
payload: {id: response.id, data: response}
});
}
// this handles the "DATA_FROM_SERVER" action
handleDataFromServer(action) {
this.cache[action.payload.id] = action.payload.data;
this.emit("change"); // or whatever you do to re-render your app
}
}