Fix async switches #110
Reference in New Issue
Block a user
Delete Branch "fix/async-switches"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
If any switches are async, the subsequent code will execute before the switches are finished. This commit moves all that code to a new function, and debounces the calls to onSwitch() so it only executes once, after all the switches finish.
Fizes #72.
@tremby What do you think?
Will read and respond tomorrow.
The concept of this PR is great and it solves a couple of issues I was having that were caused by
onSwitchcallingparseDOMmultiple times before I was ready (I have an async switch and I'm using a custom switch method to dynamically opt certain links out of Pjax).In testing with my project, however, I found that there's a slight logic flaw that needs correcting, which I've commented on inline. Once that's sorted though, this looks like it'll be really useful. Nice work!
Incrementing
numPendingSwitchesin this loop doesn't work -- as the switch functions are executed in the same loop, multiple synchronous switches will result innumPendingSwitchesbeing incremented to1and then immediately decremented to0by the new code inonSwitch. This then results inafterAllSwitchesbeing called more than once and throwing an error on the second run as at that point the state has been reset, resulting inthis.state.optionsbeingnull.It would seem safe to instead assign
numPendingSwitchesbefore any looping, on the very first line of the method, like so:If one or more of the selectors aren't present in the switched document, the check made for each selector for
newEls.length !== oldEls.lengthwill throw an error and result in a full page reload being triggered.I've tested this modification with my current project that has 3 synchronous and 1 async switches and it works for me, but obviously further testing would be required.
Because this method refers to
this.statea lot, you could do the following for brevity and readability:This is just personal preference, but I find it makes things a bit easier to parse visually.
Good point.
Good point.
Actually, it's more complicated than that, since it's not just one per selector, it's also times the number of
newEls.Good point 😉 You're quite right, I missed that.
Might need to loop
selectorstwice then: once to build thenumPendingSwitchescorrectly, and once to fire off the switch callbacks. This works for me (I cache the querySelector operation results in a map to avoid unnecessary extra queries in the second loop):Alternatively, you could stick to one loop of
selectorsand build a queue of switch callbacks to execute after the loop is done andnumPendingSwitcheshas been calculated.See what you think of either of those approaches...
Maybe the queue approach is more elegant:
Even better, we don't need to increment
this.state.numPendingSwitchesmanually. Instead, once all the switches are queued up, we can just dothis.state.numPendingSwitches = queue.length.@MoOx Can you add @robinnorth as a collaborator?
I'm not sure I can help. The PR description sounds valuable, but the changeset is too broad for my knowledge of the internals of this project. Bowing out.
LGTM
I would avoid mutation if possible and prefer a new object here.
@BehindTheMath added!
Thanks @BehindTheMath, @MoOx!
I've added one small extra commit that hopefully takes care of your comment regarding state mutation, @MoOx.
Everything looks good to me, so I think you're good to merge, @BehindTheMath 👍
Mutation of what?
this.state. Imo it's better to avoid a mutation and create a new state (in case you want to compare a state change, it's easier to do state === prevState)
I am mostly doing react this days, and I was thinking about this https://reactjs.org/docs/react-component.html#shouldcomponentupdate :)
I'm still not sure what you mean.
stateis just a name I picked for the property to store the currenthrefandoptions, so we can get it after the switches call back. After that, it gets destroyed (here). We could have usedthis.currentHreffor the same results.We could pass them around instead of making it a global property, but that gets messy and confusing.
@tremby Thank you for your insightful comments in #72.
Unless you mean we should do
instead of
That's what I mean. But you will probably need to copy the other state key/value.
Anyway, if you feel it's totally useless, feel free to move forward and ignore my comment. I am just quickly reviewing here as I currently don't use this library on any active project.
I don't see any reason to need the state once we're done with it, or to keep track of previous states, so I'm going to leave it for now.
@robinnorth Thanks for all the comments and help.