Two Weird Tricks with Redux
jlongster.com
jlongster.com
I tweeted my feelings on this last week:
2015: reflux flummox redux marty alt fluxible…
2016: redux-actions redux-act redux-forms redux-promise redux-ui…
https://twitter.com/tlrobinson/status/717258992989306880The list is over at https://github.com/markerikson/redux-ecosystem-links if you want to look.
Of you can write stateless functional components and have async and not use them, but the disadvantages of components without lifecycle methods appear much greater if you're just using vanilla React.
For the first one, if a burger is meant to be consumed differently than fries (serial versus parallel) they why are they using the same mechanism with the burger having a hacked on predicate , service type, dispatch, etc? Why wouldn't the burger just have a workflow that makes sense for how its invoked? For instance if the burger is tied to the UI and a backend request that must happen one at a time then the UI would disallow or provide a way of queueing which I guess this sorta supports but it seems forced and doesn't look like a normal flow.
The second part, ignoring async responses. I don't understand this part. Why can't you just replace your state and drop the existing handlers so nothing async from the previous state occurs? Does redux just not support this out of the box or is the author doing something very edge-case-y?
When I don't want to handle an async response anymore I remove the handle's association with the event it was going to handle. It works like this in native DOM, jQuery, even my own bias data point: msngr. But I'm curious why wouldn't redux be able to handle the same case?
> When I don't want to handle an async response anymore I remove the handle's association with the event it was going to handle. It works like this in native DOM, jQuery, even my own bias data point: msngr. But I'm curious why wouldn't redux be able to handle the same case?
In our case we use the same connection but it switches "contexts" and we want to ignore everything from a previous context. In simpler situations you could make sure the your connection is destroyed before the UI is destroyed, yes.
Sure. It was just an example. My main point was the change here felt like a hack and they forced a mechanism that should be processed serially into one that is meant to handle parallel.
> In our case we use the same connection but it switches "contexts" and we want to ignore everything from a previous context. In simpler situations you could make sure the your connection is destroyed before the UI is destroyed, yes.
So wouldn't it be best to sever ties that will cause issues and reconnect them versus anything else? That's typically the pattern you use when you want to replace event handlers. The way in the article just wastes a lot of CPU cycles to support an awkward pattern I've never seen someone use before.
Just my 2 cents. But like I said I've never used Redux so maybe these patterns are normal here.
After first impressions, I'd say it's worth a look if you're running into anything near as complicated as talked about here. It's very easy to reason about and test. gaearon - the creator of redux - seems to approve of it as well.
Saga is making side-effects (workflows) possible by subscribing to events.
So in this case I have a saga waiting for 'SEARCH_FORM_UPDATE', then it waits for 300ms, then starts the ajax request flow: a request start action, then a request complete action (or request error).
The code reads almost like synchronous code once you understand yield.
It also allows you to listen to actions that you do not control: we wanted to do something whenever a specific redux-router action happened. You can't do this with thunks, we would have needed a middleware to do this. With sagas you can simply hook up a flow to the action.
If you really want to pass around the promise, it's doable. To save in your component state, return the promise from your thunk so it gets returned by dispatch (thom_nic just posted this also). To save in your store, dispatch a 'save' action from the thunk with the promise as an argument. But I agree that it's probably a bad design.
The request sequence IDs are useful for many situations where you need to order or cancel async requests. If you have multiple in-flight requests for an autocomplete field, you only want to set isLoading=false when the last request completes, and you don't want the result of a later request to overwrite the result of an earlier request if the responses come back out-of-order.
1. send "Start" action
2. await for result of fetch("http://example.com/api/call");
3. send success or error action
this way redux is 100% synchronous and I have an obvious place for throttling requests, keeping trace of promises, etc.
BTW, the "Ignoring Async Responses" section here is a useful pattern, even if you're not working on browser developer tools ;). I've been working on a web app and have a polling timeout that I cancel on logout. Not quite the same, but logout triggers a reset of all the state obtained while authenticated, but the timeout ids are also in the store and canceled before being cleared.
"In an asynchronous world, you have 3 actions indicating asynchronous work: start, done, and error. In our system, they all are of the same type (like ADD_BREAKPOINT) but the status field indicates the event type."
This is a great suggestion (I have used a very similar schema before, although in flux), and should be the standard suggestion in redux tutorials.
Not enforcing this can lead to a proliferation of inconsistently named constants, especially if a team is learning flux/redux and has not yet settled on a naming schema.
And it is in the redux tutorials, but buried in the advanced section. http://redux.js.org/docs/advanced/AsyncActions.html
Can you explain this further? Our team uses the ACTION, ACTION_SUCCESS, ACTION_FAILURE pattern, and we haven't run into any issues. I like it because it's a bit easier to parse the sequence of actions in the console at a glance, and async actions are more clearly distinguished from synchronous ones. The main downside is having a bit more boilerplate.
I think that as a follow up, you might consider whether stores should respond to multiple ACTION's. You may find that all of the actions are forms of "take this item with id: X and replace it with this updated item" or an array of such commands
[1] https://github.com/jmakeig/redux-thunk-scaffolding/blob/mast...
* if the action creator is passed a UUID, it includes that UUID in all of the start|succeed|error events it dispatches.
* There is a request store that sits on top of the base store, which watches for events with UUIDs. If it finds one, it stores in its own map UUID => { state: start|succeed|done, IDs: [Array of IDs that came back from the AJAX request] }
* the request store exposes UUID => { state, array of resources } (i.e. it talks to the base store for you)
* Components that know exactly which resource they need (e.g. id=1) continue listening to the base store
* Components that want the results of an ajax call pass a new UUID every time their filter state changes, and thus can be certain that their UI matches the data they are displaying
https://github.com/mozilla/gecko-dev/blob/master/devtools/cl...
Tests can wait for an action to be dispatched and the promise is resolved with that action, and tests can inspect it.
The EAT_BURGER seems slightly odd, as i've never hit a situation where I would allow a second one to be fired while the first is still pending. I.E. upon EAT_BURGER.start i would disable the UI for the EAT_BURGER action to be fired. I guess that's an idealised world and you've hit something more complicated that requires it though, and the solution is good
I particularly like the async call tracking - very simple and neatly solves your issue!
Cheers
I put together a simple example here: https://gist.github.com/thom-nic/fbedf27a5222a8490aa9028a40b...
Basically: component fires a 'delete' action, and then does page navigation when delete is successful. (Not saying this is a good idea to do it that way, just that you can do it.) Or did I miss his point?
I would love to know what those scenarios were.
So far it's always been a UI optimization like that.
You could handle this at the connection layer, but I liked how it integrates with actions better (it gives a finer control over when actions are dispatched)