batchUpdates does not work as expected
See original GitHub issueThis was a feature we have been looking forward to! (We currently use our own not-so-good solution to make some of our state updates atomic) Yet I suspect it is not working as we expected it to behave (maybe we are expecting too much but reading the inline docs and code this is how I think it should be). Consider this simple example;
// fetch is a generic action we use to make async network calls, it does a setState and updates a value on store
dispatch(fetch)
// then the consumer derives a new value and stores it
.then((data) => setState(deriveNewData(data)))
With this, we do two independent setState
s one in the generic action and one inside .then
and thus we will have two independent updates; one with fetch status (e.g isCompleted: true
) and one with the derived data. We are currently deferring the first set state to the next event loop via setTimeout
and this provided a better behaviour on average. (i.e we do not set isCompleted
before data is ready anymore, but now data is updated earlier. This is just a temporary hack on our side. We were planning to implement a middleware next if this was not on the library itself.
I expected defaults.batchUpdates = true;
to batch these two updates, but it is not the case. The schedule
method never has a QUEUE
larger than 1 and I suspect this is because of Promise.resolve().then
not deferring the callback to the next event loop but instead is processed on the same phase, before the consumer’s then
is executed. See here, chrome should behave very similar to that;
generally, when the event loop enters a given phase, it will perform any operations specific to that phase, then execute callbacks in that phase’s queue until the queue has been exhausted or the maximum number of callbacks has executed.
Which implies an immediately resolved Promise
’s success callback is added to the current phase when the outerHandler
is first executed. Even if res
is called before it, the engine will register the inner function before the outer handler as it has already started executing the outerHandler
and will not defer the inner promise to the next loop.
new Promise(function outerHandler(res) {
res();
Promise.resolve().then(() => console.log('inner resolved'));
}).then(() => console.log('outer resolved'))
Issue Analytics
- State:
- Created 3 years ago
- Comments:6
It’s great to get your feedback on this new behaviours! Indeed the current batching method is very “dumb” and the gain is mostly around multiple actions triggered is sequence: it allows Sweet-state to batch them (something hard to do with a middleware).
The good news is that I think I’ve found a better way: by using React
scheduler
. If you tryreact-sweet-state@next
, the batching in reality schedules the updates, and it works much better because it’s handled by (and with) React. It means however that it is no longer “just batched”: React will decide if it has time to render each individual state update or batch them together, and also how far in the future. This might result in different “results” based on how busy the thread is, but it should be always the “best” decision.I’m still tweaking the priorities, as the current
@next
build will wait until 5s before triggering state updates, which is a bit too much and could screw up components in some cases. I’m testing usingUserBlockingPriority
that has a wait time of up to 250ms, which is a much better default and should handle your case quite well.In case you are curious, this is the PR https://github.com/atlassian/react-sweet-state/pull/108 and you can install
@next
version and change the imported priority fromscheduler
to see what I mean.It is working totally fine, it was a configuration issue the second time 👍