Angular query: Optimistic updates are not synchronous
Describe the bug
Here's a stackblitz that repro's the issue about 20% of the time (I think it's timing related): https://stackblitz.com/edit/stackblitz-starters-hn17w1mv?file=src%2Fmain.ts,src%2Findex.html
If you click "edit" and change the "whatever" to just "1", then tab out to trigger blur, you'll see the "whatever" show up for 1 frame. I think this is happening because the optimistic update (which should set the title to "1") is not happening until the next frame. So when I set the "isEditingTitle" signal to false, it flickers the old title for 1 frame. I would expect the optimistic update to be immediate and synchronous, like how normal signals behave, so this flicker never happens.
Generally speaking, this can happen whenever you have a UX that let's the user preview an edit before committing it. In this case, the preview is the input text box, and commiting it is blurring. It would be even more noticeable if the input field looked exactly like the non-editing text.
What are people's thoughts on this? Is this not a use case many encounter?
Your minimal, reproducible example
https://stackblitz.com/edit/stackblitz-starters-hn17w1mv?file=src%2Fmain.ts,src%2Findex.htmlhttps://stackblitz.com/edit/stackblitz-starters-hn17w1mv?file=src%2Fmain.ts,src%2Findex.html
Steps to reproduce
- Click the "edit" button
- Change "whatever" to just "w"
- Press Tab to trigger blur and finish editing.
Expected behavior
20% of the time, you'll see "whatever" show up for 1 frame. Desired behavior: I would expect the optimistic update to immediately take effect, and the text immediately shows "w".
How often does this bug happen?
Sometimes
Screenshots or Videos
No response
Platform
- OS: macOS
- Browser: Google Chrome, Version 141.0.7390.54 (Official Build) (arm64)
Tanstack Query adapter
angular-query
TanStack Query version
^5.69.1
TypeScript version
^5.6.3
Additional context
No response
i have a workaround that's OK-ish: read the signal, but ignore the value - instead, get the latest value from the cache directly, so use getQueryData. reading the signal is still good, so your update code is subscribed to it. but when other signals trigger an update, like isEditingTitle, reading the cache will get the optimistically updated value correctly.
the worst part of this solution, however, is you now have to be careful about the order of your signal sets: your mutation must come first, then your other signals. not great. (UPDATE: actually the ordering may not matter, due to how Angular runs effects. it doesn't immediately run an effect when you update a signal, it'll just queue it up, so you should get the updated cache value always.)
thanks for chiming in, @wook95 ! can you clarify which version that code is from? it's surprising that moving that line would fix it, cuz it doesn't look like anything async is happening in the body at all. unless you removed some of that stuff?
Sorry, I need to retract my previous comment - I made a mistake in my analysis.
I can confirm @stevesan 's workaround works well. The downside is having to manually add all related signals as dependencies.
Another option is setTimeout(() => this.isEditing.set(false), 0) which works without the computed wrapper, but feels a bit hacky.
Would be great if this could be handled at the library level, similar to React Query's useSyncExternalStore.
makes sense, @wook95 . i think another solution, which i have yet to try, is to use "queueMicrotask" for notification scheduling: https://tanstack.com/query/latest/docs/reference/notifyManager#notifymanagersetscheduler. i will try this and report back!
ok, i think setScheduler works! here it is: https://stackblitz.com/edit/stackblitz-starters-cwxlsq6x?file=src%2Fmain.ts
now the question is, are there any reasons to NOT do this? it's an example in the docs, with no warnings, so hopefully it's fine to do?
if people aren't aware of any issues with this fix, then perhaps the only action item here is to update the docs? i'm happy to update the angular query docs, although it seems this fix is not angular-specific.