urql-graphql / urql

The highly customizable and versatile GraphQL client with which you add on features like normalized caching as you grow.
https://urql.dev/goto/docs
MIT License
8.57k stars 444 forks source link

Vue: Promisified useQuery resolves to late with refreshAuth in @urql/exchange-auth #3392

Closed tvartom closed 9 months ago

tvartom commented 10 months ago

Describe the bug

@urql/vue and @urql/exchange-auth

When await useQuery is used in a component-setup, which make the component async, it doesn't work with @urql/exchange-auth.

If the auth has expired and refreshAuth is called, the promise from useQuery will resolve to late or never.

See the Stackblitz for reproduction. In the reproduction seems like the promise is never resolved. (Don't know if vue can kill it when it reloads the component.) In this reproduction, it actually is resolved next time the same continent is clicked again. (Far to late)

I use this in a application that use ssr, and need the async nature of it. In my application the promise eventually resolves, at the same time as the next update (eg next navigation click from the user), which only can be seen in the console log.

I've also tried to use const handle = useClientHandle(); and handle.useQuery with no luck.

Thanks for great job with urql!

Reproduction

https://stackblitz.com/edit/vitejs-vite-qfw3cx?file=src%2FApp.vue

Urql version

@urql/exchange-auth: 2.1.6 @urql/vue: 1.1.2 vue: 3.3.4 urql/core: 4.1.3

Validations

tvartom commented 10 months ago

Made some debugging: I don't actually think it affects this, but there is an isPromise() function in Vue-core which check for .then and .catch. The PromiseLike that useQuery return doesn't have .catch.

(I havn't been able to test if it would change anything if it was present.) Update: This is actually not the problem.

https://github.com/vuejs/core/blob/b8fc18c0b23be9a77b05dc41ed452a87a0becf82/packages/shared/src/general.ts#L52~~

tvartom commented 9 months ago

I have made some more investigation.

The problem appear when the "token" is stored in a reactive object.

So to make the code work in the reproduction is just to change the line:

const token = reactive({ expire: Date.now() + ttlToken }); to const token = { expire: Date.now() + ttlToken };

In the reproduction, it is not a problem. In my application the token is actually used for more and exists in a pinia-store. Note that the problem appear as soon as willAuthError just read token.expire, writing doesn't seem to be a problem. However a workaround is probably easy, moving the expire-information outside the reactive store.

So this is probably just a problem with the nature of Vue's update-cycle. Maybe it should be mention in the documentation to be careful with using reactive objects inside async setups inside exchange. I note the exact same problem with both @urql/exchange-auth and @urql/exchange-context.

tvartom commented 9 months ago

Closing this since it probably not a bug in Urql.

Everything works as expected if no reactive objects is read by willAuthError (in @urql/exchange-auth) or getContext (in @urql/exchange-context).

kitten commented 9 months ago

Hiya, sorry for not having caught up on this in time! I believe I must've dismissed the notification and forgot to come back to this. Also, thanks for posting a comprehensive explanation here for what's going on. I wonder whether there's a way to isolate the internal exchange function calls from Vue's reactivity model. I reckon that the read kicks off a new dependency, and if that could be avoided, that'd be much less confusing 🤔