wobsoriano / trpc-nuxt

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

Devtools breaks when using it inside a component and some other errors #74

Open cannap opened 1 year ago

cannap commented 1 year ago

When using

i have this in a component

const { $client } = useNuxtApp()
const addRecipe = async () => {
 await $client.recipes.create.mutate(recipe.value)

the error is throwing but when i submit something it just works backend.js:749 TypeError: client[procedureType] is not a function i also had trycatch and so around same behavior

image

when i remove the code above it works The Component where i use it https://github.com/cannap/recipes/blob/main/components/common/CommonRecipeWidget.vue

Trpc: https://github.com/cannap/recipes/blob/main/plugins/0.trpc.ts

thanks

Moonlight63 commented 1 year ago

I am also having the same problem. In fact, even using the example in the playground errors out with TypeError: client[procedureType] is not a function. I am unable to use trpc with this module. Documentation is also very lacking.

Edit: after further testing, I now understand slightly better. I can use trpc to retrieve data, but still any components that use the client just produce errors in the devtools, which effectively means I just can't use it because I really need my devtools to work.

edit edit: This only seems to be a problem when you access the client from top level of component. If you move the logic inside of onMounted like so:

import type { AppRouter } from '@/server/trpc/routers'
const hello = ref<inferRouterOutputs<AppRouter>['hello'] | null>(null)
onMounted(async () => {
  const { $client } = useNuxtApp()
  const { data: todos, pending, error } = await $client.hello.useQuery({ text: 'client' })
  hello.value = todos.value
})

then everything is fine. The only problem now is that it makes the other refs like pending basically useless. If you were to create a new ref outside of onmounted to hold the value of 'pending', you would only be able to set the value of pending after the await statement, at which point you would no longer be pending, unless I am missing something here? Instructions unclear. Please advise.

Edit Edit Edit: OK SO... I have narrowed down the cause/solution, but it's a weird one. This code results in an error at runtime:

const { $client } = useNuxtApp()
const { data: hello } = await $client.hello.useQuery({ text: "client" })

This code also results in an error:

const client = useNuxtApp().$client
const { data: hello } = await client.hello.useQuery({ text: "client" })

But this code runs perfectly fine with no errors, including in the devtools:

const { data: hello } = await useNuxtApp().$client.hello.useQuery({ text: "client" })

wtf...

Also, using a composable also works perfectly fine...

// composables/useGreeting.ts
export default async function useGreeting(name: string) {
  const { $client } = useNuxtApp()
  return await $client.hello.useQuery({ text: name })
}
// pages/index.ts
const { data:hello, refresh } = await useGreeting("client")

So... Yeah.. That makes no sense to me. If someone wants to explain how perfectly functionally identical code is behaving completely differently... That would improve my sanity.

cannap commented 1 year ago

@Moonlight63 did this happens inside components or pages? i had no time try if the same happens when we use it in page or in a component

Moonlight63 commented 1 year ago

@cannap This was in a page. I have not tried inside a component, but I would expect the result to be the same since pages are just components anyway. I'll give it a shot.

nicolassutter commented 1 year ago

@cannap @Moonlight63 :wave: Did you ever find out what caused this behavior ?

After investigating a bit it seems it is this line in @trpc/client, createTRPCClientProxy.ts that breaks.

return (client as any)[procedureType](fullPath, ...args)

I built @trpc/client locally and added this snippet bellow right before the return statement, my application's code still worked and my DevTools didn't break 😅

if (!client[procedureType]) {
  console.log(procedureType, client);
  return;
}

return (client as any)[procedureType](fullPath, ...args)

My console.log gave the following result:

image

It definitely seems like procedureType cannot index client in some cases (that I have not determined yet).

Hope this is of use to someone !

reslear commented 7 months ago

patch base on @nicolassutter solution:

diff --git a/src/createTRPCClientProxy.ts b/src/createTRPCClientProxy.ts
index a4586de4f436e49724fd1b15aefe0a843e7e79cf..23166e54ed661165db34d3231facdbf0fe92e460 100644
--- a/src/createTRPCClientProxy.ts
+++ b/src/createTRPCClientProxy.ts
@@ -122,6 +122,10 @@ export function createTRPCClientProxy<TRouter extends AnyRouter>(

       const fullPath = pathCopy.join('.');

+      if (!client[procedureType]) {
+        return;
+      }
+      
       return (client as any)[procedureType](fullPath, ...args);
     });
   });

but it seems to have stopped working

malcock commented 6 months ago

I was able to create a slightly ugly, but easy work around this based on what @Moonlight63 said. You can create a composable that returns the $client object inside a function and it'll work. I wonder whether this method would create a memory leak or something though - maybe could pair it with vueuse sharedComposable?

// ./composables/useTrpc.ts
export default () => {
  const { $apiClient } = useNuxtApp();

  return () => $apiClient;
};

You can use it like so:

// mycomponent.vue
<script setup lang="ts">
import useTrpc from "~/composables/useTrpc";

const tprc = useTprc();

const updateUser = async (id,name) =>{
await tprc().user.updateUser.mutate({
    id,
    name
  });
}
</script>
...