-
-
Notifications
You must be signed in to change notification settings - Fork 51
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Does / should revertData
have old data as well?
#416
Comments
Very good question! Actually you did it in the optimal way, passing the old data (current from perspective of time before a request is fired). Indeed old data is not passed to
const oldData = getOldData(store.getState());
action = {
...action,
meta: {
...action.meta,
mutations: Object.entries(action.meta.mutations).reduce((prev, [queryType, mutation]) => {
mutation = {
...mutation,
revertData: mutation.revertData && data => mutation.revertData(data, oldData)
};
prev[queryType] = mutation;
return prev;
}, {}),
},
} so basically in theory we could overwrite revertData by passing oldData in the middleware, which then will be correct due to the way JS closures work. I am not sure though we really should do it due to potential problems this could cause with parallel requests. Maybe this could be fine though, together with a proper warning somewhere. If you are interested, I wrote more on this topic here https://dev.to/klis87/taming-network-with-redux-requests-part-6-optimistic-updates-4ig5 Wondering whats your opinion on all this! |
Tbh, I never thought about race conditions in concurrent requests. That definitely seems to make things super-complicated. Your post makes sense. I think it's safest to pass in old data to the config and use that in the Thanks much for replying in detail :) p.s: I'll keep this open in case anyone else wants to comment their thoughts on it but please feel free to close this issue. |
Thx for your answer! And I will keep this opened, this is kind of question/potential feature request. Concurrency is a risk, but there are many cases in apps where they won't happen, for example button could be disabled on pending and so on, so no parallel mutations would be even possible. Then, oldData passed by the lib might be handy, it always would save some job on the app level in such scenarios. |
@chandru89new I have another idea, with your suggestion, actually in case of not passing |
I also plan adding thins like new effect, right now we have |
This was my first thought when I started playing with |
I'm thinking about this and running some (possibly edge-case) scenarios. Overall, seems like a nice idea. I don't understand some bits though.
(Also, apologies if some of these are already answered by understanding the codebase; I havent had the time/chance to go through the codebase and understand how things are designed, yet). Feel free to let me know if that's the case and I'll happily dive in :) |
Sounds naive but why not this rule? If you use |
I am wondering, maybe an option like
No, because R#2 would just join R#1, no 2nd request would not be fired at all, and promise of dispatched R2 would resolve with the same as R1
This is a very good idea, I think this is yet another effect, like Regarding
Take leading would solve this as R2 wouldn't be fired at all. However, if Queues would also help in this scenario, even without To sum up, in my opinion this library should provide sensible defaults and to have enough declarative configuration so any real-life case like above could be handled with a set of properties, for example mixture of an effect and |
Btw, it seems that I will need to implement |
What's interesting, adding queue effect and takeLeading, we will match RXJS operators for most common AJAX use cases - https://dev.to/rxjs/about-switchmap-and-friends-2jmm |
We also match redux-saga, apart from queue/concatMap, I believe this would be needed to implement on user land in sagas. Anyway, what I think is this:
For now we support takeLatest and takeEvery. TakeLatest is the default for queries and takeEvery for mutations, but I am pretty sure that defaults could be set dynamically depending on circumstances, like described above. Then defaults would be sensibly chosen, but always possible to override |
For the reference, this might be more problematic actually, let's try something complicated!
Thanks to queues I think this would cover all the cases, but this would require many unit tests not to make any mistake. The reward will be high though, we could automate optimistic updates without any risk of race conditions with reverts. This is the direction this lib should take, declarative and reliable by default, but still configurable. |
What is also interesting, above pattern should take into consideration that queue would be per type, so instead of like, unlike action, we should have setLike action, the point is to move all mutations which could suffer from race condition under one action, with a dynamic config. What we also need to take into account is the possibility to optimistically updated multiple queries at the same time, queues should be per query, or per mutation but iterated over each query to properly revert data. Another one, automatic normalisation must be also solved here. |
@klis87 |
@khalilbenachir please provide an example what you want to achieve - an action example and how you use it, and what you would like to abstract in a middleware |
if you mean that you don't want to define old data at all, but rather to make it returning old data automatically, I would need to think about it, but please confirm that this is what you need. otherwise, please give me an example |
@klis87
|
@khalilbenachir for now revertData doesn't even have 2nd arg passed, this is TODO for the version 2.0. For now, you could do sth like this: const mapMutations(mutations, store) {
// in this function you can map mutations, like
if (mutation.updateDataOptimistic && !mutation.revertData) {
const currentState = getQuery(store.getState(), { type: queryKey })
mutation.revertData = () => currentState.data // ofc remember not to mutate it, but do it in redux way
}
};
const autoRevertMiddleware = store => next => action => {
if (action.meta && action.meta.mutations) {
return next({
...action,
meta: {
...action.meta,
mutations: mapMutations(action.meta.mutations, store),
}
})
}
return next(action);
} This is not a working code, and |
@klis87 i added a middleware that to autoRevert , but i can't solve my problem, so my question is why when i dispatch a mutation action [POST_BOOK] , i didn't add any mutations to the action, but when the mutation triggered, she also updated the queries [FETCH_BOOK], so how can that happen if u have any idea? |
@khalilbenachir I need all actions and the middleware code to help you. |
@khalilbenachir I dont see any FETCH action in your example. Anyway, I believe I could help you only if you provided me an example repo with reproduction. Especially that putConnaissanceClient doesn't even have meta.autoRevert prop |
In the examples shown for
revertData
in the optimistic data updates, I find that it takes only one parameter and that parameter is the latest local data (afterupdateDataOptimistic
has run).For a use case where I am trying to edit some resource, I run into this issue where I am not able to
revertData
unless I also pass the older data down.Example:
In this case's
revertData
, I need access to old/previous data for the given book which is being edited.Is this already available (and I am missing it)? Or, if it's not available and the only solution is to pass the older data down to the dispatch action (like
editBook = (editedBook, oldBook))
), is it possible to add the previous data as the second arg torevertData
?The text was updated successfully, but these errors were encountered: