Where should HTTP requests be initiated in Flux? - reactjs

There is a plenty of discussion on how to communicate with external services in Flux.
It is pretty clear that the basic workflow is to fire an HTTP request, which will eventually dispatch successful or failure action based on the response. You can also optionally dispatch "in progress" action before making the request.
But what if request's parameters depend on store's state? Nobody seems to mention it.
So essentially, based on user interaction with the view, an ACTION is dispatched. Store owns logic on how to transition from current state0 to the next state1 given ACTION. Data from state1 is needed to properly form new HTTP request.
For example, user chooses a new filter on the page, and store decides to also reset pagination. This should lead to a new HTTP request with (new filter value, first page), not (new filter value, current page from state0).
View can not make the HTTP call itself right with user's interaction because it then would have to duplicate store's logic to transition to the next state.
View can not make the HTTP call in its store's onChange handler because at this point it is no longer known what was the origin of the state change.
It looks like a viable option to make store fire the HTTP request in the action handler, after it transitioned to the next state. But this will make this action implicitly initiating HTTP call, which disables neat possibility to have a replayable log of dispatched actions for debugging.
Where should HTTP requests be initiated in Flux?

Let's start at the bottom:
It looks like a viable option to make store fire the HTTP request in the action handler, after it transitioned to the next state. But this will make this action implicitly initiating HTTP call, which disables neat possibility to have a replayable log of dispatched actions for debugging.
This can be mitigated by not initiating HTTP requests if you're in debugging/replay mode. This works great as long as the only thing you do in your HTTP request handlers is fire actions (e.g. SUCCESS and FAILURE actions). You could implement this with a simple global boolean (if (!debug) { httpReq(...) }), but you could also make the pattern a bit more formal.
In Event Sourcing parlance, you use Gateways for such purposes. In normal operation, the Gateway makes your HTTP requests, and in debugging, you turn the Gateway off (so it doesn't make any HTTP requests).
That said, I think the problem can actually be solved by rethinking where your HTTP requests are made.
So essentially, based on user interaction with the view, an ACTION is dispatched. Store owns logic on how to transition from current state0 to the next state1 given ACTION. Data from state1 is needed to properly form new HTTP request.
In the second link in your question (Where should ajax request be made in Flux app?), I recommend doing your writes in action creators but reads in the stores. If you extrapolate that pattern into your use case, you might end up with something like this (pseudocode and long variable names for clarity):
class DataTable extends React.Component {
render() {
// Assuming that the store for the data table contains two sets of data:
// one for the filter selection and one for the pagination.
// I'll assume they're passed as props here; this also assumes that
// this component is somehow re-rendered when the store changes.
var filter = this.props.filter;
var start = this.props.start;
var end = this.props.end;
var data = this.props.dataTableStore.getDataForPageAndFilter(
start, end, filter
);
// the store will either give us the LOADING_TOKEN,
// which indicates that the data is still loading,
// or it will give us the loaded data
if (data === DataTableStore.LOADING_TOKEN) {
return this.renderLoading();
} else {
return this.renderData(data);
}
}
}
class DataTableStore {
constructor() {
this.cache = {};
this.filter = null;
this.start = 0;
this.end = 10;
}
getDataForPageAndFilter(start, end, filter) {
var url = HttpApiGateway.urlForPageAndFilter(start, end, filter);
// in a better implementation, the HttpApiGateway
// might do the caching automatically, rather than
// making the store keep the cache
if (!this.cache[url]) {
this.cache[url] = DataTableStore.LOADING_TOKEN;
HttpApiGateway.query(url)
.then((response) => {
// success
var payload = {
url: url,
data: response.body
};
dispatch(DATA_FETCH_SUCCESS, payload);
}, (error) => {
// error
dispatch(DATA_FETCH_FAIL, { ... });
});
}
return this.cache[url];
}
handleChangeFilterAction(action) {
this.filter = action.payload.filter;
// the store also decides to reset pagination
this.start = 0;
this.end = 10;
this.emit("change");
}
handleDataFetchSuccessAction(action) {
this.cache[action.payload.url] = data;
this.emit("change");
}
handleDataFetchFailAction(action) {
// ...
}
}
DataTableStore.LOADING_TOKEN = "LOADING"; // some unique value; Symbols work well
You can see that the store is responsible for deciding how to update the pagination and the filter variables, but is not responsible for deciding when HTTP requests should be made. Instead, the view simply requests some data, and if the store doesn't have it in the cache, it will then make the HTTP request.
This also allows the view to pass in any additional local state into the getter (in case the HTTP requests also depends on local state).

I'm not sure to understand all the parts of the question but will try to answer with some useful insights.
Flux is a bit like a not-yet mature version of EventSourcing / CQRS / Domain-Driven-Design for frontend developers
We use something akin to Flux for years on the backend with a different terminology. We can compare Flux ActionCreators to DDD Commands, and Flux Actions to DDD Events.
A command represent the user intent (LOAD_TIMELINE(filters)). It can be accepted or rejected by a command handler that will eventually publish some events. In an UI this does not make much sens to reject commands as you don't want to display buttons that the user should not click...
An event represent something that has happened (always in the past).
The React app state that drives the UI is then somehow a projection of the event log to a json state. Nothing can be displayed on the UI without an event being fired first.
Answering your questions
But what if request's parameters depend on store's state? Nobody seems
to mention it.
In DDD, command handlers can actually be stateful. They can use the app state to know how to handle the command appropriately. Somehow this means that your Flux ActionBuilders can be stateful too (maybe they can use some store data while )
So essentially, based on user interaction with the view, an ACTION is
dispatched. Store owns logic on how to transition from current state0
to the next state1 given ACTION. Data from state1 is needed to
properly form new HTTP request.
In DDD there is a concept called Saga (or Process Manager).
To make it simple, it receives the event stream and can produce new commands.
So basically you can express through a Saga your requirement: when there's an event "FILTERS_UPDATED", fire a command "RELOAD_LIST" with the new filters. I'm pretty sure you can implement something similar with any Flux implementation.
Sagas should rather be disabled when you replay the event log, as replaying the event log should not have side effects like triggering new events.
These kinds of semantics are supported in my Atom-React framework, where stores can act as stateful command handlers or sagas.

Related

Which is more preferable? Dispatch the response of a promise or dispatch an action stating a promise needs to be triggered?

Here are two code samples:
onClick() { // click-handler of a button
axios.get(someUrl)
.then(response => {
// setData is a fn dispatching an action-creator passed through react-redux's connect()
setData(response.data);
});
}
or
// buttonClicked is also a fn dispatching an action-creator
// Difference being the middle-ware handles the entire async process
<button onClick={this.buttonClicked}>Click me</button>
The latter method will use Axios in some middleware, and then dispatch another action which will set the response data in the store.
So this means that the first approach will only dispatch one action, while the second approach will dispatch two actions.
Both ways obviously seem to work, but I would like to know what the best way would be. Is there a downside to either approach?
Disclaimer: This is an opinionated answer, and somewhat rambly.
The thing about promises is that they work the way a human being would think of a promise. So use them like that in your program. Typically IMO you should only use Promises when you know that an event would occur in the normal course of your program workflow, or when you are promising a result.
So for example if you ask for a socket connection, I promise to give one to you whenever I am able to, you don't have to wait for me, just go on and do your thing, as soon as I have done everything needed to get that back to you I will hand it to you; and you can move on in your workflow from the point that needs it. For example, (pseudo code):
var Socket = new Promise(function(resolve, reject) {
resolve(do_something_to_get_a_socket());
});
Socket.then(authenticate()).then(sendData());
etc.
Sticking a promise to an event handler like onClick should be a promise to do something for the user — use it in your code to create threads that will do the heavy lifting of complex processing, while the user is still able to interact with the interface.
For example, in a game a click could fire a dart and you promise that it will animate on the screen (and even if that glitches) you still promise that it will hit the target etc, but the user doesn't have to wait for the promise to be fulfilled to fire another dart.
So use Promises to make your program more readable by you and other coders, and use it to make workflow of your program more realistic to your usecase.
I heavily recommend something like: https://www.npmjs.com/package/redux-api-middleware
This middleware (or others like it) contain quite a few features you would most likely have to write yourself if you were to implement this with just axios in a callback. For example, it will automatically dispatch request, success, and failure actions based on the AJAX call's result.
When dispatching this action using the middleware, many things are taken care of for you.
{
[CALL_API]: {
endpoint: "http://example-api.com/endpoint",
method: "GET",
headers: { ... },
types: [
"GET_X_REQUEST", "GET_X_SUCCESS", "GET_X_FAILURE"
]
}
}
Something like this will automatically fire a "GET_X_REQUEST" action when it begins to load. Then a success or failure action (with appropriate data or error objects attached as a payload) when the AJAX call completes or fails.
Or any similar middleware where Redux ends up handling the entire async process.

ReactJS fetching new data on prop

As a preface, I'm still new to React, so I'm still fumbling my way through things.
What I have is a component that fetches data to render an HTML table. So I call my Actions' fetchData() (which uses the browser's fetch() API) from within componentWillMount(), which also has a listener for a Store change. This all works well and good, and I'm able to retrieve and render data.
Now the next step. I want to be able to fetch new data when the component's props is updated. But I'm not exactly sure what the proper way to do so is. So I have a three part question
Would the proper place to do my fetchData() on new props be in componentWillReceiveProps(), after validating that the props did change, of course?
My API is rather slow, so it's entirely possible a new prop comes in while a fetch is still running. Is it possible to cancel the old fetch and start a new one, or at least implement logic to ignore the original result and wait for the results from the newer fetch?
Related to the above question, is there a way to ensure only one fetch is running at any time besides having something like an isLoading boolean in my Action's state (or elsewhere)?
Yes, componentWillReceiveProps is the proper place to do that.
Regarding point 2 and 3:
The idea of cancelling the task and maintaining 'one fetch running' seems to be inadequate. I don't think this kind of solution should be used in any system because implementation would limit an efficiency of your app by design.
Is it possible to cancel the old fetch and start a new one, or at least implement logic to ignore the original result and wait for the results from the newer fetch?
Why don't you let a 'newer fetch' response override an 'old fetch' response?
If you really want to avoid displaying the old response you can implement it simply using a counter of all fetchData calls. You can implement it in this way:
var ApiClient = {
processing: 0,
fetchData: function(){
processing++
return yourLibForHTTPCall.get('http://endpoint').then(function (response)){
processing--
return response
}
},
isIdle: function(){
return processing == 0
}
}
and the place where you actually make a call:
apiClient.fetchData(function(response){
if(apiClient.isIdle()){
this.setState({
})
}
}
I hope yourLibForHTTPCall.get returns a Promise in your case.

How to deal with query params in react + react-router + flux

I'm trying to replace a Backbone.Marionette App to React and am facing difficulty thinking about query params. I think I'm missing a really simple peace in understanding this pattern so I apologize if this question is totally nonsense. I would appreciate any support or just pointing me to some direction that I can google more specifically.
There's a /users page which lists users and you can filter the users via search bar. So if you want to filter the users which contain 'joe' in their username, I would make a request to the server with query params like /users?username=joe. In addition I am able to paginate by adding a page parameter, too (/users?username=joe&page=1).
If I only think about the functionality, the flow would probably be
The Client inserts joe to the input element and clicks Search.
Clicking the Search button fires an Action (like Action.getUser).
The Action makes a request to the server and receives the results
The Dispatcher dispatches a function with the results payload to whomever (usually the Store) is interested in the Action.
The Store's state changes with the new result received by the Action
The View (Component) re-renders by listening to the Store's change.
and it works as expected. However, I would like the Client to be able to bookmark the current filtered result and be able to come back to the same page some time later. This means I will need somewhere to save explicit information about the search term the Client made, which is usually the url (am I right?). So I will need to update the url with query parameters to save the search term (/users?username=joe&page=1).
What I'm confused is where and when to update the url? What I can come up with right now are the 2 options below - and they don't seem to be clean at all.
Option 1
The Client inserts joe to the input element and clicks Search.
Clicking the Search button fires a transition of the ReactRouter with the new query params (/users?username=joe&page=1).
The View (Component) receives the new params via this.props.params and this.props.query.
The View (Component) fires an Action like Action.getUser depending on the query params it receives - in this case username=joe&page=1.
after this, it is the same as above
Option 2 (only 6 is different from what I explained above)
The Client inserts joe to the input element and clicks Search.
Clicking the Search button fires an Action (like Action.getUser).
The Action makes a request to the server and receives the results
The Dispatcher dispatches a function with the results payload to whomever (usually the Store) is interested in the Action.
The Store's state changes with the new result received by the Action
The View (Component) re-renders by listening to the Store's change. And somehow (I don't know how, yet) updates its url depending on its props (like this.props.searchusername, and this.props.searchpage)
What is the best practice on handling query params? (or this may not be specific to query params)
Am I completely misunderstanding the design pattern or architecture? Thanks in advance for any support.
Some articles I've read
Any way to get current params or current query from router (outside of component)?
Async data and Flux stores
Make it easier to add query parameters
React Router and Arbitrary Query Params: Page Refreshes Unintentionally on Load?
Add default params?
I would consider best practice to be the submit button only setting the location query (username). The rest should be taken care by the main react component that is assigned as router component. By this, you can be sure that anytime one revisits or shares the url, they can get the same results. And this is very generic too.
Something like this:
let myQuery = this.props.location.query;
if (myQuery.username) {
let theUser = myQuery.username;
this.setState({
userName = myQuery.username
});
} else {
this.setState({
userName = false //Show All
});
}
And then use this state "userName" to send to the server to search with. By this way, you will not need to iterate the code of the component that takes care of listing users since server already sends the relevant data.
In my experience with using location queries in React, I have been very content with their reactivity cycles and performance. I'd highly recommend keeping every different app state in relevance with the url.
Not entirely sure what you mean by
this means I will need to update the url to save the information (/users?username=joe&page=1).
You will probably have a similar structure to this.
TopContainer.jsx
-- Users.jsx
-- a list of User.jsx
Usually TopContainer will watch all the stores and if anything changed, pass it down to users.jsx. That way in Users.jsx, you can simply render this.props.users without worrying about any reRendering.
The search users actions usually happens in TopContainer's componentWillMount event, and you the page will listen to UserStore. That's a good place to throw in any query params. Something like this would work
componentWillUnmount() {
let searchTerm = router.getCurrentQuery().searchTerm;
UserActions.searchUsers(searchTerm)
},
The page doesn't really care if the url has a query params or not, it just dumbly shows whatever in the user store.
Then when the search finishes, Users.jsx will be reloaded and show the correct results

Accessing react-router from flummox action/store

I want to be able to make an API call in a Flummox action and transition differently depending on the response. I can pass the router into the action call but am looking for advice on a potentially better way.
UPDATE:
The correct answer is below but I wanted to add some detail to this.
I'm doing an isomorphic app that 1. needs to get data from an api and 2. may need to redirect based on the api response. Whatever I do needs to work through an express.js app and through react.
I made a small lib that does the api call and return some results. I pass it an object (query params object from express for the server-side or a similar object I create for the react-side). This lib makes the request, determines if a redirect is needed and passes back errors, path (string), redirect (boolean), and data (json).
In express, if the redirect boolean is true, I just redirect to it with the current query params. If it's false, I pass the data to flux through an action which updates a store. I then renderToString with react, serialize stores so the clint-side can bootstrap, and send a rendered page to the client.
In react, the redirect boolean isn't important, I get the response back from my lib, pass the data to my flux action, and just transition to whatever the path is. There's really no notion of redirection. Just go to the path no matter what.
Hopefully this is helpful to someone.
In my setup I have my own router module which just wraps the instance of react-router that I create at startup. That makes it easy for any part of the application to just require that module and do what it needs to.
But I would advise you not to have side effects like a call to the router inside your actions. Actions should concern themselves on mutating your application state, and nothing more. It should be possible to call the same action from anywhere in your application which needs to perform the mutation that the action encapsulates.
So if you're switching routes during an action, you're basically tying that action to that particular use case. Let's take an example. You have a todo list, with an input box at the bottom to add a new todo. For that use case, it might be useful to switch route after you saved the todo. Perhaps you switch to Recent Todos or something. But then a new use case comes along where you want to be able to add new todos during another workflow, perhaps the user is planning her week and should be able to add todos on different days. You want the same action that adds a todo, but you certainly don't want to switch routes because the user is still planning the week.
I haven't used Flummox a lot, but from my understanding your Flux object returns whatever the action returns when you trigger an action. So instead of putting the route change inside your action, make sure to return the response from the action and let your component decide if the route should be changed. Something like this:
// todo-action.js
class TodoActions extends Actions {
createMessage(todo) {
return TodoStore.saveToServer(todo);
}
}
// todo-list.js
const TodoList extends React.Component {
render() {
...
}
addTodo(todo) {
this.props.flux.addTodo(todo).then(response => {
if (response.some.prop === someValue) {
this.props.router.transitionTo(...);
}
});
}
}
That way, the action is still nicely decoupled from the route change. If you want to do the route switch in more than one place, you could encapsulate that in a addTodoAndSwitchRoute method in your Flux class.

Nesting Flux Data

In my app, I make two ajax calls to for one piece of data. First I make a call to get a list of ecommerceIntegrations. Once I have those, I can then grab each of their respective orders.
My current code looks something like this:
componentDidMount: function() {
EcommerceIntegrationStore.addChangeListener(this._onIntegrationStoreChange);
OrderStore.addChangeListener(this._onOrderStoreChange);
WebshipEcommerceIntegrationActionCreators.getEcommerceIntegrations();
},
_onIntegrationStoreChange: function() {
var ecommerceIntegrations = EcommerceIntegrationStore.getEcommerceIntegrations();
this.setState({ecommerceIntegrations: ecommerceIntegrations});
ecommerceIntegrations.forEach(function(integration) {
WebshipOrderActionCreators.getPendingOrdersOfIntegration(integration.id);
});
},
_onOrderStoreChange: function() {
this.setState({
pendingOrders: OrderStore.getAllPendingOrders(),
pendingOrdersByIntegration: OrderStore.getPendingOrdersByIntegration()
});
}
I'm trying to follow Facebook's Flux pattern, and I'm pretty sure this doesn't follow it. I saw some other SO posts about nesting data with Flux, but I still don't understand. Any pointers are appreciated.
Everything here looks good except this:
ecommerceIntegrations.forEach(function(integration) {
WebshipOrderActionCreators.getPendingOrdersOfIntegration(integration.id);
});
Instead of trying to fire off an action in response to another action (or worse yet, an action for every item in ecommerceIntegrations), back up and respond to the original action. If you don't yet have a complete set of data, and you need to make two calls to the server, wait to fire the action until you have all the data you need to make a complete update to the system. Fire off the second call in the XHR success handler, not in the view component. This way your XHR calls are independent of the dispatch cycle and you have moved application logic out of the view and into an area where it's more appropriately encapsulated.
If you really want to update the app after the first call, then you can dispatch an action in the XHR success handler before making the second call.
Ideally, you would handle all of this in a single call, but I understand that sometimes that's not possible if the web API is not under your control.
In a Flux app, one should not think of Actions as being things that can be chained together as a strict sequence of events. They should live independently of each other, and if you have the impulse to chain them, you probably need to back up and redesign how the app is responding to the original action.

Resources