How do you manage expensive derived calculations in a "flux" application - javascript

I am currently working on a prototype application using the flux pattern commonly associated with ReactJS.
In the Facebook flux/chat example, there are two stores, ThreadStore and UnreadThreadStore. The latter presents a getAll method which reads the content of the former synchronously.
We have encountered a problem in that operations in our derived store would be too expensive to perform synchronously, and would ideally be delegated to an asynchronous process (web worker, server trip), and we are wondering how to go about solving this.
My co-worker suggests returning a promise from the getter i.e.
# MyView
componentDidMount: function () {
defaultState = { results: [] };
this.setState(defaultState);
DerivedStore.doExpensiveThing()
.then(this.setState);
}
I'm not entirely comfortable with this. It feels like a break with the pattern, as the view is the primary recipient of change, not the store. Here's an alternative avenue we've been exploring - in which the view mounting event dispatches a desire for the derived data to be refreshed (if required).
# DerivedStore
# =========================================================
state: {
derivedResults: []
status: empty <fresh|pending|empty>
},
handleViewAction: function (payload) {
if (payload.type === "refreshDerivedData") {
this.state.status = "pending"; # assume an async action has started
}
if (payload.type === "derivedDataIsRefreshed") {
this.state.status = "fresh"; # the async action has completed
}
this.state.derivedResults = payload.results || []
this.notify();
}
# MyAction
# =========================================================
MyAction = function (dispatcher) {
dispatcher.register(function (payload) {
switch (payload) {
case "refreshDerivedData":
doExpensiveCalculation()
.then(function(res) {
dispatcher.dispatch({
type: "derivedDataIsRefreshed",
results: res
})
})
);
}
});
};
# MyView
# =========================================================
MyView = React.createClass({
componentDidMount: function () {
if (DerivedStore.getState().status === "empty") {
Dispatcher.dispatch("refreshDerivedData");
}
},
getVisibility: function () {
return DerivedStore.getState().status === "pending" ? "is-visible" : ""
},
render: function () {
var state = DerivedStore.getState()
, cx = React.addons.classSet
, classes = cx({
"spinner-is-visible": state.status === "pending"
});
return <div {classes}>
<Spinner /> # only visible if "spinner-is-visible
<Results results={state.derivedResults}/> # only visible if not...
</div>;
}
});
# MyService
# =========================================================
# ensure derived data is invalidated by updates in it's source?
OriginalStore.addListener(function () {
setTimeout(function () {
dispatcher.dispatch({
type: "refreshDerivedData"
})
}, 0);
});
What I like about this approach is that the view treats the DerivedStore as it's view model, and views of this ilk are primarily interested in the freshness of their view model. What concerns me however is the potential for stores coming out of sync.
My question(s) then:
is the promise approach acceptable?
is the second approach better/worse? If so, why?
is there an existing "canonical" approach to this problem?
PS: sorry if there are any fundamental linting errors in this code, I've been working in Coffeescript for the last 3 months and it's destroyed my linting powers...

All async actions should be caused by the creation of an action. The completion of an async action should be signaled by the creation of another action. Stores may listen to these actions, and emit a change event.
In your component you listen to a DerivedStore for changes. An action can be created from anywhere, such as in your component or another store. The data is (eventually) derived, the store is updated, a change event is emitted, and your component(s) apply the event payload to state.
All in all, your component doesn't actually know if what's happening behind the scenes is sync or async. This is great because it allows you to make these performance changes behind the scenes without risk of breaking your components.
Pure stores usually only have one public function which gets the state of the store. In your components you should only call this in getInitialState, or better yet: have a mixin which does this and adds the change listener for you.

It sounds like a combination of the following discussions on github could help you.
store.getItem() which may require an async server call:
https://github.com/facebook/flux/issues/60
Managing amount of client-side data:
https://github.com/facebook/flux/issues/62
Essentially getting the store data is synchronous, the component could then tell the store to do the long running task but then forgets about it.
Once the task is completed in the store an action is created and the flow thing happens at which time the component can synchronously get the required information from the store.
Does that make sense?

If I was going to create an async process in the most Flux way possible, I would approach it much like an XHR request -- kick off the async process in either the Action Creator or the Store (whichever makes the most sense for the app) and then call a new Action Creator to dispatch a new action when the async process completes. This way, multiple stores can respond to the completed expensive async process, and the data flow is still emanating from an Action.

You could also just add a handler to your Store that gets called when a certain event is emited in your store
So lets say in your store you have a method:
Store = { ...
addUnreadDoneListener:function(callback){
this.on(SOME_EVENT_CONSTANT, callback);
},
...}
In your componentWillMount you can register to this "addUnreadDoneListener" with a function of your component, which then gets called everytime your store emits this certain event.
I personally do this aswell in my project. And I think its pretty easy to manage this way
Hope this helped.
EDIT: I forgot to mension... I use Eventemitter to do this.

Related

Do Vuex actions have to modify or use the store state?

Is it good practice to use Vuex store actions to perform related asynchronous operations (e.g., GET requests) without actually modifying the state of the store?
I have a Vuex store. Let's call it Host.
It contains an object as its state, with some getters to retrieve various forms of the state as well as some mutations to modify said state.
However, when it comes to actions, I perform certain asynchronous requests on host objects which I pass in to the actions as a parameter. For instance, a Host can be enabled or disabled.
I, therefore, have an action hostEnable(host), which calls an Axios GET request which responds only with an OK (200).
const getDefaultState = () => {
return {
host: {...}
}
};
export const state = getDefaultState();
const getters = {
getHost: (state) => {
return state.host;
},
...
};
const mutations = {
setHost: (state, host) => {
state.host = host;
},
...
};
const actions = {
fetchHost ({commit}, hostId) => {
api.get(hostId)
.then(({data}) => {
commit(setHost, data);
})
.catch(error => {
throw new Error(error);
});
},
createHost: ({state}) => {
return api.create(state.host);
},
hostEnable: (context, host) => {
return api.enableHost(host);
},
...
};
export default {
state,
getters,
actions,
mutations
};
Is it fine to use a Vuex store in this way, or do all actions have to either use or modify the store state?
Is it fine to use a Vuex store in this way, or do all actions have to
either use or modify the store state?
In this scenario, yes, it's perfectly fine and no, it doesn't have to modify anything.
Even though, it's not gonna behave in the way that a Vuex action is intended to work (since technically, actions are supposed to work with mutations in some fashion), you can define hostEnable as an action because it makes more sense to group all Host related business logic in one single module rather than putting it somewhere else in your codebase.
So yeah, you can use it to perform asynchronous operations without committing any mutations to your store data since it's also responsible for containing complicated business logic in your application.
Lastly, using actions for asynchronous logic is one of the high-level principles when structuring your Vue application.
Application-level state is centralized in the store.
The only way to mutate the state is by committing mutations, which are
synchronous transactions.
Asynchronous logic should be encapsulated in, and can be composed with
actions.
Key concepts:
State are to store your data.
Mutations are to handle sync operations to you data.
Actions are to handle async operations (that's why you receive a context object instead state as params )
Getters are to get data and mutate it ( i.e. get the host that contains ip from canada, and so on )
Actions are supposed to change/mutate the state only,
while Getters return data based on the state without mutating the state. See https://github.com/vuejs/vuex/issues/46#issuecomment-174539828
Store actions are for business logic without return statements. For instance, someStore.createXXX may make API call, then update list of items(state) with the response of that call and display notifications.
For just API calls we have API layer classes/functions e.g. productApi.js, userApi.js.
As a bonus, I recommend to have multiple appropriate stores(e.g. blog post store, comment store, author store) instead of having one huge global store with unrelated stuff in it.

Where should things like the current state of an async action be stored in a react-redux application?

I have a login popup which maps a 'isLoggingIn' boolean to the redux store. When a login request action is dispatched a saga intercepts the action and sends another action that the login is processing, the reducer will take that in and set the 'isLoggingIn' boolean to true.
My store:
export interface AppState {
playerToken:string,
loginOpen: boolean,
loginProcessing: boolean
}
The login saga:
function* loginUser(action: any) {
yield put({ type: (LOGIN + PROCESSING) });
try {
const response = yield call(apiCall, 'api/token', 'POST', { username: action.payload.username, password: action.payload.password });
if (response)
{
yield put({ type: (LOGIN + SUCCESS), payload: response.data });
}
catch ({ statusCode }) {
if (statusCode === 401) {
yield put({ type: (LOGIN + FAIL), payload: { error: "Invalid username or password" } })
}
console.log(statusCode);
}
}
Once the saga is done with the login if there's an error it dispatches an action which the reducer sets to a 'loginError' string in the store and sets the isLoggingIn to false, otherwise isLoggingIn is set to false and the user login id is set which prompts the popup to hide itself (i.e. isVisible={this.props.playerToken == undefined).
This seems insanely complicated but I'm not sure how to break this down using Redux principles. I feel strongly the isProcessingLogin should be part of the components state, but the component has no real idea what's going on after it sends the login attempt event and there's no way for it to ever know unless it's listening on for something in the props.
It gets much worse with the various crud operations which need to happen and the various 'isCreatingXModel' booleans which have to be set to true/false in the store and mapped correctly in components.
Is this how redux is supposed to work or am I over using it in places it doesn't belong?
If this is how redux is supposed to be used what are its benefits exactly? I've read online a lot about things which make sense like having a single point of truth, but they can all be done without the crazy redux bloat, I've read people say not to use redux until you need it but that means I'm going to be doing api calls in two conceptually separate areas of code when redux is integrated whenever I 'need it', finally one of the biggest advantages I see purported by advocates is its ability to rewind and move forward in time, which is great but it won't work in any live application which connects to a database in the backend it manipulates unless as part of rewinding there's an undo last api call action.
Keep in mind that these are all entirely my opinions.
1. You might not need sagas (or thunk or other 'async' redux plugin)
Remember that redux is state management only. The API calls can be written in vanilla javascript with or without redux. For example: here's a basic replication of your flow without sagas:
e.g.
import { setLoadingStatus } from './actions'
import { store } from './reducers' // this is what is returned by a createStore call
export function myApiCall(myUrl, fetchOptions) {
store.dispatch(setLoadingStatus('loading'))
return fetch(myUrl, fetchOptions)
.then((response) => {
store.dispatch(setLoadingStatus('succeeded', data))
// do stuff with response data (maybe dispatch a different action to use it?)
})
.catch((error) => {
store.dispatch(setLoadingStatus('failed', error))
// do stuff
})
}
Note the use of store.dispatch. There's an interesting notion in React-Redux that you can only dispatch actions with mapDispatchToProps, but fortunately, that's not true.
I replaced your multiple actions with one that takes a state and optional data. This'll reduce the number of actions and reducers you need to write, but your action history will be harder to read. (Instead of three distinct actions, you'll only have one.)
2. You might not need redux.
The example function above could look basically identical if you weren't using redux -- imagine replacing the store.dispatch calls with this.setState calls. In the future, when you added it, you'd still have to write all the reducer, action, action creator boilerplate, but it would be only slightly more painful than doing it from the start.
As I said above, I usually go with Redux when working with React the built-in state management is has a bad mental map with any sort of large app.
There are two opposing rules of thumb:
use redux for state that needs to be shared between components
use redux for all state and get a single source of truth
I tend to lean to the second one. I hate hunting down errant pieces of state in the leaves of a large React component tree. This is definitely a question with no "correct" answer.

Where to put context specific multi-step async logic in Redux

tl;dr I would like to know where to place context specific multi-step async callback logic in a redux architecture, and if I am on the right track with the example code I supply below. By "multi-step" and "context specific" I typically mean server calls initiated by some user action (onClicks, etc) where the logic might only be relevant for a given component (such as redirect to a given route when successful).
The redux docs has this to say on code with side effects:
In general, Redux suggests that code with side effects should be part of the action creation process. While that logic can be performed inside of a UI component, it generally makes sense to extract that logic into a reusable function so that the same logic can be called from multiple places—in other words, an action creator function.
While that seems fine, I am not totally sure whether it is "correct" to put calls to my routing component in there, as these action creators usually seem quite generic, and triggering routing to some other resource in the app is usually quite context dependant.
I also find it a bit weird to put these quite-different beasts, that trigger action creators asynchronously and dispatch the resulting actions, in the same files (foo-model/actions.js) as the "clean" sync action creators. Is this the right place? When reading tutorials on Redux it seems like they live side by side.
The example code is quite simple and basically describes these steps:
On a user click, call a function with some param
This function calls another async function (such as a network call)
When the async call completes, trigger a routing action to another page
Background: I want to gradually refactoring a Meteor project by moving all Meteor specific bits out of the React components, eventually substituting Meteor in the front and back for something else. As there are about 50KLOC I cannot do this in one go, so I am gradually working my way through one route at a time, hoping to end up with a standard React+Redux+ReduxRouter package. In the current code routing, data fetching, and rendering is somewhat intertwined in each component, and I am having some trouble finding out where to put multi-step async logic, such as the example below.
Details on the stack I am trying to wrangle my way out of:
FlowRouter for routing
Meteor/MiniMongo for data mutation and retrieval
React Komposer for Higher Order Components
old Meteor code in MyContainerComponent
// triggered as onClick={(e) => this.saveEncounter(e.target.value)}
// in render()
const saveEncounter = (encounter) => {
Meteor.call('createEncounter', encounter, handleSaveResult);
}
};
const handleSaveResult = (err, encounterId) => {
if (err) {
this.setState({errorMessages: err});
} else {
// route to another page
NavigationActions.goTo('encounter', {encounterId: this.props.encounter._id || encounterId});
}
}
new redux code - moved into actions.js
I am trying to keep the implementation straight forward (no additional deps) at this point to understand the foundations. "Simplification" using redux-thunk, redux-actions or redux-saga need to come later. Modeled after the example code in the Redux tutorial for Async Actions
export const saveEncounter = (encounter) => {
function handleSave(err, encounterId) {
if (err) {
dispatch(createEncounterFailure(err), encounter);
} else {
dispatch(createEncounterSuccess(encounterId));
}
}
dispatch(createEncounterRequest(encounter));
Meteor.call('createEncounter', encounter, handleSave);
}
// simple sync actions creators
export const CREATE_ENCOUNTER_REQUEST = 'CREATE_ENCOUNTER_REQUEST';
function createEncounterRequest(encounter) {
return {
type: CREATE_ENCOUNTER_REQUEST,
encounter
};
}
export const CREATE_ENCOUNTER_FAILURE = 'CREATE_ENCOUNTER_FAILURE';
function createEncounterFailure(error, encounter) {
return {
type: CREATE_ENCOUNTER_FAILURE,
error,
encounter
};
}
export const CREATE_ENCOUNTER_SUCCESS = 'CREATE_ENCOUNTER_SUCCESS';
function createEncounterSuccess(encounterId) {
return {
type: CREATE_ENCOUNTER_SUCCESS,
encounterId
};
}
As you noted in a comment, Dan Abramov discussed a lot of the ideas behind handling async work in Redux in his answer for how to dispatch an action with a timeout. He also wrote another excellent answer in why do we need middleware for async flow in Redux?.
You might want to read through some of the other articles in the Redux Side Effects category of my React/Redux links list to get a better idea of ways to handle async logic in Redux.
In general, it sounds like you may want to make use of either "sagas" or "observables" for managing some of your async logic and workflow. There's a huge variety of Redux middlewares for async behavior out there - I summarized the major categories and most popular libraries in my blog post The Tao of Redux, Part 2 - Practice and Philosophy. There's also some interesting thoughts on a very decoupled saga-based Redux architecture in a post called Redux Saga in Action.
I see your point, you would like to have a way to divide and categorize your actions, is that right? Actions that will execute sync code, async code, logger, etc.
Personally, I use some naming convention. If I have to dispatch an action that has to fetch some data, I call it REQUEST_DATA. If have to store some data arrived from the server to the ReduxStore, I call it STORE_DATA.
I don't have a specific pattern. I also have to point that I divide my codebase based on the feature, so the modules where I define my actions are pretty small and neat
In my experience with Redux, I haven't found any problems with putting async calls inside action creators. I think redux-thunk or some other middleware is very helpful, even for a simple setup.
The only thing I'd add is that I don't find your sample code very readable.
Personally I've come to like the ducks pattern, but also just keeping action types, action creators and reducers in separate files would work to improve clarity.
Hope this helps.

ReactJS: How to handle component state when it's based on AJAX?

Summary: New to ReactJS and I'm trying to figure out the best way to update a component when it's state depends on a remote API (i.e. keep component state in sync with remote database via AJAX API).
Example Use Case: Think of a product inventory where clicking a button adds a product to your cart and decrements the inventory by 1. Every time the user clicks it initiates an AJAX request and then upon completion of the request, the component re-renders with the new product inventory by calling setState().
Problem: I've ran into an issue where because both setState() and the AJAX request are asynchronous, the component becomes out of the sync with the server. For example if you click really quickly you can initiate more than one AJAX request for a single product ID because the component's state has not yet updated to reflect that the product ID is no longer in inventory. I have a simple example below to illustrate the concept:
Inadequate Solution: This could be handled on the server side by sending an error back if the client request a product that is no longer in inventory, however I'm really looking for the best way to handle this common scenario in ReactJS on the client side and to make sure I'm understanding the best way to handle component state.
Component extends React.Component {
constructor(props) {
super(props);
this.state = {
clicksLeft: 0,
};
}
componentDidMount() {
//getClicksLeft is async and takes a callback, think axios/superagent
getClicksLeft((response) => {
this.setState(response);
});
}
btnClicked = () => {
//This may appear redundant/useless but
//imagine sending an element in a list and then requesting the updated
//list back
const data = {clicks: this.state.clicksLeft--};
decrementClicksLeft(data, () => {
getClicksLeft((response) => {
this.setState(response);
});
}
}
render() {
<button onClick={this.btnClicked}>Click me {this.state.clicksLeft} times</button>
}
}
Is there any reason to have to call getClicksLeft when the button is clicked? You have already called it when the component is mounted and then anytime the button is clicked you just decrement that number by one.
btnClicked = () => {
if (this.state.clicksLeft > 0) {
decrementClicksLeft();
this.setState({clicksLeft: this.state.clicksLeft - 1});
}
}
This would work if there is only one user trying to buy stuff at a time. Otherwise you could also check the amount left before making the purchase.
btnClicked = () => {
getClicksLeft((response) => {
if (response > 0) {
decrementClicksLeft();
this.setState({clicksLeft: this.state.clicksLeft - 1});
}
});
}
This way if there are no clicks left, nothing happens.
The most basic solution would be to disable the button while you wait for the response to come back:
(I've also made your code simpler.)
Component extends React.Component {
constructor(props) {
super(props);
// Initial state
this.state = {
clicksLeft: 0, // No clicks are availabe
makeRequest: false, // We are not asking to make a request to the server
pendingTransaction: false, // There is no current request out to the server
};
}
componentDidMount() {
// Initial load completed, so go get the number of clicks
this._getClicksRemaining();
}
// Called whenever props/state change
// NOT called for the initial render
componentWillUpdate(nextProps, nextState) {
// If there is no existing request out to the server, AND if the next
// state is asking us to make a request (as set in _handleButtonClick)
// then go make the request
if (!this.state.pendingTransaction && nextState.makeRequest) {
const data = {
clicks: this.state.clicksLeft--,
};
// decrementClicksLeft is async
decrementClicksLeft(data, () => this._getClicksRemaining());
// First fire off the async decrementClicksLeft request above, then
// tell the component that there is a pending request out, and that it
// is not allowed to try and make new requests
// NOTE this is the one part of the code that is vulnerable to your
// initial problem, where in theory a user could click the button
// again before this setState completes. However, if your user is able
// to do that, then they are probably using a script and you shouldn't
// worry about them. setState/render is very fast, so this should be
// more than enough protection against human clicking
this.setState({
makeRequest: false,
pendingTransaction: true,
});
}
}
_getClicksRemaining() {
// getClicksLeft is async
getClicksLeft((response) => {
// Here we are inside of the callback from getClicksLeft, so we
// know that it has completed. So, reset our flags to show that
// there is no request still pending
const newState = Object.assign(
{
pendingTransaction: false,
},
response,
);
this.setState(newState);
});
}
// The button was clicked
_handleButtonClick = () => {
if (!this.state.pendingTransaction) {
// If there isn't a request out to the server currently, it's safe to
// make a new one. Setting state here will cause `componentWillUpdate`
// to get called
this.setState({
makeRequest: true,
});
}
}
render() {
// Disable the button if:
// * there are no clicks left
// * there is a pending request out to the server
const buttonDisabled = ((this.state.clicksLeft === 0) || this.state.pendingTransaction);
return (
<button
disabled={buttonDisabled}
onClick={this._handleButtonClick}
>
Click me {this.state.clicksLeft} times
</button>
);
}
}
After spending some time with react-redux, redux-thunk and redux-pack I decided to go with something simpler: react-refetch. I didn't really need the complexities of redux as I am only doing post and get operations on lists. I also need some simple side effects like when I do a post, I need to update multiple lists (which is achieved through andThen() in react-refetch).
This solution has much less boiler plate and works great for small projects. The core reason to choose this project over react-redux can be summarized in this quote from heroku's blog entry:
Looking around for alternatives, Redux was the Flux-like library du jour, and it did seem very promising. We loved how the React Redux bindings used pure functions to select state from the store and higher-order functions to inject and bind that state and actions into otherwise stateless components. We started to move down the path of standardizing on Redux, but there was something that felt wrong about loading and reducing data into the global store only to select it back out again. This pattern makes a lot of sense when an application is actually maintaining client-side state that needs to be shared between components or cached in the browser, but when components are just loading data from a server and rendering it, it can be overkill.
1: https://github.com/heroku/react-refetch
2: https://engineering.heroku.com/blogs/2015-12-16-react-refetch/

Flux / Fluxible: Updating routes based on state change

What is the most concise way to trigger route changes based on a change to a state store, using Fluxible and react router?
An example component might take some user input and call an Action on a click event (shortened for brevity)
class NameInput extends React.Component {
constructor (props) {
super(props);
this.state = props.state;
this.handleClick = this.handleClick.bind(this);
}
handleClick (event) {
this.context.executeAction(setName, {name:'Some User Value'});
}
render () {
return (
<div>
<input type="button" value="Set Name" onClick={this.handleClick} />
</div>
);
}
}
export default Home;
The handleClick method executes an Action which can update a Store with our new value.
But what if I also want this to trigger a navigation after the Store is updated? I could add the router context type and directly call the transition method after executing the Action:
this.context.executeAction(setName, {name:'Some User Value'});
this.context.router.transitionTo('some-route');
But this assumes that the setName Action is synchronous. Is this conceptually safe, on the assumption that the new route will re-render once the Action is completed and the Store is updated?
Alternatively, should the original Component listen for Store Changes and start the route transition based on some assessment of the store state?
Using the Fluxible, connectToStores implementation, I can listen for discreet changes to Store state:
NameInput = connectToStores(NameInput, [SomeStore], function (stores, props) {
return {
name: stores.SomeStore.getState().name
}
});
How a could a Store listener of this type be used to initiate a Route change?
I've noticed in my own application that for these kinds of flows it's usually safer to let actions do all the hard work. Annoying thing here is that the router isn't accessible from actions.
So, this is what I'd do:
Create a meta-action that does both: setNameAndNavigate. As payload you use something like this:
{
name: 'Value',
destination: {to: 'some-route', params: []},
router: this.context.router
}
Then, in your action, do the navigating when the setName completes.
It's not ideal, especially the passing of the Router to the action. There's probably some way to attach the Router to the action context, but that's not as simple as I had hoped. This is probably a good starting point though.
Extra reading:
Why do everything in actions? It's risky to execute actions in components in response to store changes. Since Fluxible 0.4 you can no longer let actions dispatch inside another dispatch. This happens a lot faster than you think, for example, executing an action in response to a change in a store, without delaying it with setImmediate or setTimeout, will kill your application since store changes happen synchronously during a dispatch.
Inside actions however, you can easily execute actions and dispatches, and wait for them to complete before executing the next one.
The end result of working this way is that most of your application logic has moved to actions, and your components turn into simple views that set-and-forget actions only in response to user interactions (clicks/scrolling/hover/..., as long as it's not in response to a store change).
The Best way is to create a new action as #ambroos suggested,
setNameAndNavigate. For navigation though, use the navigateAction
https://github.com/yahoo/fluxible/blob/master/packages/fluxible-router/lib/navigateAction.js, you would only have to give the url as argument.
Something like this,
import async from 'async';
import setName from 'some/path/setName';
export default function setNameAndNavigate(context, params, done) {
async.waterfall([
callback => {
setName(context, params, callback);
},
(callback) => {
navigate(context, {
url: '/someNewUrl',
}, callback);
}
], done);
}
let your actions be the main workers as much as possible.

Categories