You might be unwittingly opening a can of worms here, depending on how much of a perfectionist you are and how reliable you need your app to be. And if you want optimistic updates, oh boy.
Things can get way more difficult, because HTTP requests are not necessarily guaranteed to arrive in order nor are the responses from the server.
Same goes for your database, which can run into concurrency issues and so you might want to start versioning your data.
There are some strategies you can use to reduce such problems.
Write your endpoints in non stateful way or minimize the amount of sent state. For example, instead of sending list of all selected items, just send the single item that should be toggled.
Take advantage of the throttle & debounce saga effects, they will help you with request delays, skips and distribution to minimize the chances of stuff going out of order.
Consider blocking user action until you get a response back where it makes sense
Be aware that canceling a saga (e.g. through the takeLatest
effect) doesn't cancel the ajax request itself (it will still arrive on your BE no matter what). It will only cancel the saga itself (in other words it will prevent your app from running the code after you would receive the data from BE)
Also be aware that using takeLatest
to cancel sagas is limited. E.g. if you have only single entity you might always want to cancel the previous saga, because data from the previous response are no longer relevant.
yield takeLatest(DO_ACTION, actionSaga);
But what if you have the same action on top of multiple entities and you want to cancel the previous action only if you work with the same entity but not when it is another entity? If the list of entities is static, you can use the function pattern instead:
yield takeLatest(action => action.type === DO_ACTION && action.id = 'foo', actionSaga);
yield takeLatest(action => action.type === DO_ACTION && action.id = 'bar', actionSaga);
yield takeLatest(action => action.type === DO_ACTION && action.id = 'baz', actionSaga);
Once the list gets too long or if you have dynamic ids/amount of entities you will need to write your own logic using infinite cycles, id:task map & cancel effect.
export function* dynamicIdWatcher() {
const taskMap = {}
while (true) {
let action = yield take(DO_ACTION)
if (taskMap[action.id]) yield cancel(taskMap[action.id])
const workerTask = yield fork(actionSaga, action)
taskMap[action.id] = workerTask
}
}
Sorry for not really answering your question, but I hope some of what I wrote can still be useful in figuring out your use case :)