wobsoriano / trpc-nuxt

End-to-end typesafe APIs in Nuxt applications.
trpc-nuxt.vercel.app
MIT License
687 stars 39 forks source link

useQuery not reactive(watch) with ref inputs #85

Closed nconnector closed 1 year ago

nconnector commented 1 year ago

This documented way of using useQuery refreshes on count change but always sends the initial count.value

const { data } = await $client.hello.useQuery(
  { num: count.value },
  { watch: [count] }
);

The obvious workaround to get reactivity is useAsyncData and query with an input getter:

const { data } = await useAsyncData(
  () => $client.hello.query({ num: count.value }),
  { watch: [count] }
)

Am I missing something or is there no way to support dynamic inputs in useQuery?

mlbiche commented 1 year ago

I do confirm that I have the same issue. I am using useAsyncData for now but it does not seem to be a viable solution as using it with a pick array leads to a never type (see https://github.com/wobsoriano/trpc-nuxt/issues/22#issuecomment-1270863820).

wobsoriano commented 1 year ago

You can now do this in ^0.10.4

const id = ref(1)

const { data } = await $client.todo.getTodo.useQuery(id, {
  watch: [id],
})

setTimeout(() => {
  id.value++
}, 1000)

Input will accept a ref or non-ref value. If it's a ref, you'll still have to add it to the watch property to make it reactive for now.

wobsoriano commented 1 year ago

Update: You can omit the watch property if input is a ref.

const id = ref(1)

const { data } = await $client.todo.getTodo.useQuery(id)

setTimeout(() => {
  id.value++
}, 1000)

For reactive state, convert a specific property to a computed first:

const state = reactive({ id: 1 })

const id = computed(() => state.id)

const { data } = await $client.todo.getTodo.useQuery(id)

setTimeout(() => {
  state.id++
}, 1000)
apinrdw commented 11 months ago

@wobsoriano can the input accept more than 1 ref as an object? like nuxt composable useFetch the query or params options can accept multiple refs

janvennemann commented 11 months ago

@apinrdw I ran into this issue as well today, as my procedure accepts an object as input, not just a simple number. I was able to solve this by using a computed ref that i use to create a new object that holds multiple refs:

const limit = ref(15)
const currentPage = ref(1)
const queryInput = computed(() => ({
  page: currentPage.value,
  limit: limit.value,
}))

const { data } = await $client.document.all.useQuery(queryInput, { watch: [queryInput] })

@wobsoriano is this the recommended way to do this? The docs and your example only work for single ref inputs of primitive JS values. If the input is an object that holds multiple refs it will not work without the computed ref.

wobsoriano commented 10 months ago

@janvennemann SLR but that is exactly how you would do it. Libraries like vue-query suggest doing it as well. Check this thread regarding reactive options.

If you check Vue's convention and best practices around composables' input arguments, it's written there that

If you are writing a composable that may be used by other developers, it's a good idea to handle the case of input arguments being refs or getters instead of raw values.

So I think passing in an object with refs inside it is kind of breaking the convention?

warflash commented 10 months ago

Moving here from #146

So I think passing in an object with refs inside it is kind of breaking the convention?

To be honest my interpretation of that part in the docs would be the exact opposite. It says composables should be expected to also handle refs and getters and should not only be limited to raw values. To me that doesn't mean it shouldn't support passing in a payload/config that contains a mix of raw values and refs.

Since this is a module specifically for nuxt as well I think people do "expect" it to be similar in terms of usage, where useQuery would be the counterpart to useFetch.

useFetch specifically has handles mixed raw and ref values inside a query object mentioned in the docs and it's something we make use of a ton in our projects. Whenever one of the reactive params changes the composable refetches data.

Thanks for #151 btw, haven't tested it yet but that's a nice step to making reactivity feel more intuitive!

wobsoriano commented 10 months ago

@warflash I'll review and add more improvements as we proceed. Even improved https://github.com/wobsoriano/trpc-nuxt/pull/151, making getters auto-watchable as well.

For raw objects with refs inside it, I can use a utility like this and pass them to the watch property of useAsyncData. I can may be implement it in the future, but I still need to know of the trade-offs.