[How To] Properly use trpc UseQuery based on currently selected item

I have a component with a video element and a flatlist. I want to utilize a trpc query to get the videoUri based on the currently selected item. Here is my working component:
import React, { useState, useRef, FC } from "react";
import { StyleSheet } from "react-native";

import { Text } from "../../../design/typography";
import { View } from "../../../design/view";

import { TouchableOpacity } from "../../button";
import { FlashList } from "@shopify/flash-list";
import { Video, ResizeMode } from "expo-av";

interface CourseComponentProps {
data?: DataItem[];
}

type DataItem = {
ID: bigint;
post_title: string;
videoUri?: string;
};

type ItemProps = {
item: DataItem;
onPress: () => void;
};

const Item = ({ item, onPress }: ItemProps) => (
<TouchableOpacity onPress={onPress}>
<Text>{item.post_title}</Text>
</TouchableOpacity>
);

export const CourseComponent: FC<CourseComponentProps> = ({ data }) => {
const video = React.useRef(null);
const [currentItem, setCurrentItem] = React.useState<DataItem>();

const renderItem = ({ item }: { item: DataItem }) => {
return <Item item={item} onPress={() => setCurrentItem(item)} />;
};

return (
<View className="h-screen">
<Video
ref={video}
style={styles.video}
source={{
uri: currentItem?.videoUri || "",
}}
useNativeControls
resizeMode={ResizeMode.CONTAIN}
isLooping
/>
<Text>{currentItem?.post_title}</Text>
<FlashList
data={data}
renderItem={renderItem}
keyExtractor={(item) => item.ID.toString()}
/>
</View>
);
};

CourseComponent.defaultProps = {
data: [
{
ID: BigInt(1),
post_title: "Default Post Title 1",
videoUri: "https://d23dyxeqlo5psv.cloudfront.net/big_buck_bunny.mp4",
},
...
],
};
import React, { useState, useRef, FC } from "react";
import { StyleSheet } from "react-native";

import { Text } from "../../../design/typography";
import { View } from "../../../design/view";

import { TouchableOpacity } from "../../button";
import { FlashList } from "@shopify/flash-list";
import { Video, ResizeMode } from "expo-av";

interface CourseComponentProps {
data?: DataItem[];
}

type DataItem = {
ID: bigint;
post_title: string;
videoUri?: string;
};

type ItemProps = {
item: DataItem;
onPress: () => void;
};

const Item = ({ item, onPress }: ItemProps) => (
<TouchableOpacity onPress={onPress}>
<Text>{item.post_title}</Text>
</TouchableOpacity>
);

export const CourseComponent: FC<CourseComponentProps> = ({ data }) => {
const video = React.useRef(null);
const [currentItem, setCurrentItem] = React.useState<DataItem>();

const renderItem = ({ item }: { item: DataItem }) => {
return <Item item={item} onPress={() => setCurrentItem(item)} />;
};

return (
<View className="h-screen">
<Video
ref={video}
style={styles.video}
source={{
uri: currentItem?.videoUri || "",
}}
useNativeControls
resizeMode={ResizeMode.CONTAIN}
isLooping
/>
<Text>{currentItem?.post_title}</Text>
<FlashList
data={data}
renderItem={renderItem}
keyExtractor={(item) => item.ID.toString()}
/>
</View>
);
};

CourseComponent.defaultProps = {
data: [
{
ID: BigInt(1),
post_title: "Default Post Title 1",
videoUri: "https://d23dyxeqlo5psv.cloudfront.net/big_buck_bunny.mp4",
},
...
],
};
Solution:
const query = trpc.lesson.videoUri.useQuery(currentItem?.id as string, {
enabled: !!currentItem?.id,
});
const query = trpc.lesson.videoUri.useQuery(currentItem?.id as string, {
enabled: !!currentItem?.id,
});
...
Jump to solution
26 Replies
Trader Launchpad
I can use this component in a screen and pass in a {data} prop to override the items and the videos it loads. I can use a basic trpc query to fetch all posts from my 'wp_posts' table and load them into my component. The query looks like:
export const lessonRouter = router({
all: publicProcedure.query(({ ctx }) => {
return ctx.prisma.wp_posts.findMany({
where: {
post_type: "sfwd-lessons",
},
});
}),
});
export const lessonRouter = router({
all: publicProcedure.query(({ ctx }) => {
return ctx.prisma.wp_posts.findMany({
where: {
post_type: "sfwd-lessons",
},
});
}),
});
Now in my actual database the videoUri lives in another table 'wp_postmeta' and not in the same table that would have the "post_title". I understand I could modify the 'all' trpc query to add the videoUri from the 'postmeta' table into the one query. But this would add unnecessary time to loading the original flatlist which only needs the basic information like post_title. I assume it is better to call a second query inside the Component that fetches the videoUri based on the currently selected item (currentItem.ID) via a second query function like so:
export const lessonRouter = router({
all: publicProcedure.query(({ ctx }) => {
return ctx.prisma.wp_posts.findMany({
where: {
post_type: "sfwd-lessons",
},
});
}),
videoUri: publicProcedure.input(z.bigint()).query(({ ctx, input }) => {
return ctx.prisma.wp_postmeta.findFirst({
where: {
post_id: input, // Change the post_id value here
meta_key: "app_video_uri", // Replace "video_uri" with the actual meta_key for the meta_value you want to retrieve
},
});
}),
});
export const lessonRouter = router({
all: publicProcedure.query(({ ctx }) => {
return ctx.prisma.wp_posts.findMany({
where: {
post_type: "sfwd-lessons",
},
});
}),
videoUri: publicProcedure.input(z.bigint()).query(({ ctx, input }) => {
return ctx.prisma.wp_postmeta.findFirst({
where: {
post_id: input, // Change the post_id value here
meta_key: "app_video_uri", // Replace "video_uri" with the actual meta_key for the meta_value you want to retrieve
},
});
}),
});
How would I properly call this second query that only fires once currentItem has a value? I tried placing it insdie a useEffect hook with a condition of [currentItem] but got an error. This is what i tried:
React.useEffect(() => {
const fetchVideoUri = async () => {
if (currentItem?.ID) {
try {
const { data } = await trpc.lesson.videoUri.useQuery(currentItem.ID);
setVideoUri(data?.meta_value ?? undefined);
} catch (error) {
// Handle error
}
}
};

fetchVideoUri();
}, [currentItem]);
React.useEffect(() => {
const fetchVideoUri = async () => {
if (currentItem?.ID) {
try {
const { data } = await trpc.lesson.videoUri.useQuery(currentItem.ID);
setVideoUri(data?.meta_value ?? undefined);
} catch (error) {
// Handle error
}
}
};

fetchVideoUri();
}, [currentItem]);
Some research tells me that useQuery is a hook and cannot be called inside useEffect. (https://stackoverflow.com/questions/70300864/how-to-use-usequery-with-useeffect) i am also trying to use something like:
const { data, error } = trpc.lesson.videoUri.useQuery(currentItem?.id, {
enabled: currentItem?.id !== undefined,
});
const { data, error } = trpc.lesson.videoUri.useQuery(currentItem?.id, {
enabled: currentItem?.id !== undefined,
});
but have not been able to get it to work
Alex / KATT 🐱
This is the way to go
Solution
Alex / KATT 🐱
const query = trpc.lesson.videoUri.useQuery(currentItem?.id as string, {
enabled: !!currentItem?.id,
});
const query = trpc.lesson.videoUri.useQuery(currentItem?.id as string, {
enabled: !!currentItem?.id,
});
Trader Launchpad
thank you! I currently have:
const { data: videoData, error } = trpc.lesson.videoUri.useQuery(
currentItem?.ID,
{
enabled: !!currentItem,
},
);
const { data: videoData, error } = trpc.lesson.videoUri.useQuery(
currentItem?.ID,
{
enabled: !!currentItem,
},
);
and am getting error:
TypeError: Do not know how to serialize a BigInt
TypeError: Do not know how to serialize a BigInt
Alex / KATT 🐱
Add superjson
Alex / KATT 🐱
Data Transformers | tRPC
You are able to serialize the response data & input args. The transformers need to be added both to the server and the client.
Trader Launchpad
GitHub
t3-turbo-and-clerk/trpc.ts at lms · launchthatbrand/t3-turbo-and-cl...
Contribute to launchthatbrand/t3-turbo-and-clerk development by creating an account on GitHub.
Trader Launchpad
import { initTRPC, TRPCError } from "@trpc/server";
import { type Context } from "./context";
import superjson from "superjson";

const t = initTRPC.context<Context>().create({
transformer: superjson,
errorFormatter({ shape }) {
return shape;
},
});

const isAuthed = t.middleware(({ next, ctx }) => {
if (!ctx.auth.userId) {
throw new TRPCError({ code: "UNAUTHORIZED", message: "Not authenticated" });
}
return next({
ctx: {
auth: ctx.auth,
},
});
});

export const router = t.router;
export const publicProcedure = t.procedure;
export const protectedProcedure = t.procedure.use(isAuthed);
import { initTRPC, TRPCError } from "@trpc/server";
import { type Context } from "./context";
import superjson from "superjson";

const t = initTRPC.context<Context>().create({
transformer: superjson,
errorFormatter({ shape }) {
return shape;
},
});

const isAuthed = t.middleware(({ next, ctx }) => {
if (!ctx.auth.userId) {
throw new TRPCError({ code: "UNAUTHORIZED", message: "Not authenticated" });
}
return next({
ctx: {
auth: ctx.auth,
},
});
});

export const router = t.router;
export const publicProcedure = t.procedure;
export const protectedProcedure = t.procedure.use(isAuthed);
Alex / KATT 🐱
Not on a laptop so can't browse the code very well Is it a native BigInt? If it's a custom thing you have to add your own serializer Superjson handles BigInt well It might also be that you're using some query cache lib where this error comes from
Trader Launchpad
i am using trpc with prisma, and a wordpress database. shema.prisma looks like:
model wp_posts {
ID BigInt @id @default(autoincrement()) @db.UnsignedBigInt
post_author BigInt @default(0) @db.UnsignedBigInt
post_date DateTime @default(dbgenerated("'1970-01-01 00:00:00'")) @db.DateTime(0)
post_date_gmt DateTime @default(dbgenerated("'1970-01-01 00:00:00'")) @db.DateTime(0)
post_content String @db.LongText
post_title String @db.Text
post_excerpt String @db.Text
post_status String @default("publish") @db.VarChar(20)
comment_status String @default("open") @db.VarChar(20)
ping_status String @default("open") @db.VarChar(20)
post_password String @default("") @db.VarChar(255)
post_name String @default("") @db.VarChar(200)
to_ping String @db.Text
pinged String @db.Text
post_modified DateTime @default(dbgenerated("'1970-01-01 00:00:00'")) @db.DateTime(0)
post_modified_gmt DateTime @default(dbgenerated("'1970-01-01 00:00:00'")) @db.DateTime(0)
post_content_filtered String @db.LongText
post_parent BigInt @default(0) @db.UnsignedBigInt
guid String @default("") @db.VarChar(255)
menu_order Int @default(0)
post_type String @default("post") @db.VarChar(20)
post_mime_type String @default("") @db.VarChar(100)
comment_count BigInt @default(0)

@@index([post_author], map: "post_author")
@@index([post_name(length: 191)], map: "post_name")
@@index([post_parent], map: "post_parent")
@@index([post_type, post_status, post_date, ID], map: "type_status_date")
}
model wp_posts {
ID BigInt @id @default(autoincrement()) @db.UnsignedBigInt
post_author BigInt @default(0) @db.UnsignedBigInt
post_date DateTime @default(dbgenerated("'1970-01-01 00:00:00'")) @db.DateTime(0)
post_date_gmt DateTime @default(dbgenerated("'1970-01-01 00:00:00'")) @db.DateTime(0)
post_content String @db.LongText
post_title String @db.Text
post_excerpt String @db.Text
post_status String @default("publish") @db.VarChar(20)
comment_status String @default("open") @db.VarChar(20)
ping_status String @default("open") @db.VarChar(20)
post_password String @default("") @db.VarChar(255)
post_name String @default("") @db.VarChar(200)
to_ping String @db.Text
pinged String @db.Text
post_modified DateTime @default(dbgenerated("'1970-01-01 00:00:00'")) @db.DateTime(0)
post_modified_gmt DateTime @default(dbgenerated("'1970-01-01 00:00:00'")) @db.DateTime(0)
post_content_filtered String @db.LongText
post_parent BigInt @default(0) @db.UnsignedBigInt
guid String @default("") @db.VarChar(255)
menu_order Int @default(0)
post_type String @default("post") @db.VarChar(20)
post_mime_type String @default("") @db.VarChar(100)
comment_count BigInt @default(0)

@@index([post_author], map: "post_author")
@@index([post_name(length: 191)], map: "post_name")
@@index([post_parent], map: "post_parent")
@@index([post_type, post_status, post_date, ID], map: "type_status_date")
}
lesson router looks like:
import { router, publicProcedure, protectedProcedure } from "../trpc";
import { z } from "zod";

export const lessonRouter = router({
all: publicProcedure.query(({ ctx }) => {
return ctx.prisma.wp_posts.findMany({
where: {
post_type: "sfwd-lessons",
},
});
}),
videoUri: publicProcedure.input(z.bigint()).query(({ ctx, input }) => {
return ctx.prisma.wp_postmeta.findFirst({
where: {
post_id: input, // Change the post_id value here
meta_key: "app_video_uri", // Replace "video_uri" with the actual meta_key for the meta_value you want to retrieve
},
});
}),
});
import { router, publicProcedure, protectedProcedure } from "../trpc";
import { z } from "zod";

export const lessonRouter = router({
all: publicProcedure.query(({ ctx }) => {
return ctx.prisma.wp_posts.findMany({
where: {
post_type: "sfwd-lessons",
},
});
}),
videoUri: publicProcedure.input(z.bigint()).query(({ ctx, input }) => {
return ctx.prisma.wp_postmeta.findFirst({
where: {
post_id: input, // Change the post_id value here
meta_key: "app_video_uri", // Replace "video_uri" with the actual meta_key for the meta_value you want to retrieve
},
});
}),
});
Alex / KATT 🐱
It looks right to me If you want a quick ugly fix you can turn your BigInt into numbers or strings Before returning them in the proc Unsure why it's not working. I have a similar setup myself
Trader Launchpad
in lesson router its ...input.(z.bigint() and other places im using BigInt does that matter? if i try to use z.BigInt i get an error: "Property 'BigInt' does not exist on type"
Alex / KATT 🐱
Not really, if superjson is setup correctly it should work either way Both for input and output
Trader Launchpad
so where is the actual issue? in the trpc or prisma part? since it goes trpc->prisma->db if i change lessonRouter to:
videoUri: publicProcedure.input(z.number()).query(({ ctx, input }) => {
return ctx.prisma.wp_postmeta.findFirst({
where: {
post_id: input, // Change the post_id value here
meta_key: "app_video_uri", // Replace "video_uri" with the actual meta_key for the meta_value you want to retrieve
},
});
videoUri: publicProcedure.input(z.number()).query(({ ctx, input }) => {
return ctx.prisma.wp_postmeta.findFirst({
where: {
post_id: input, // Change the post_id value here
meta_key: "app_video_uri", // Replace "video_uri" with the actual meta_key for the meta_value you want to retrieve
},
});
so number rather than bigint, and then change the videoUri query with a hardcoded number
const { data: videoData, error } = trpc.lesson.videoUri.useQuery(621, {
enabled: !!currentItem,
});
const { data: videoData, error } = trpc.lesson.videoUri.useQuery(621, {
enabled: !!currentItem,
});
I can get the proper videoUri in a console.log on the screen so i have half the issue fixed, which was triggering the query based on the currently selected item. Now I seem to just be having an issue with bigint
Alex / KATT 🐱
trpc does a JSON.stringify(xxx) of the data sent and received that will fail if xxx contains anything that isn't standard json if you use a data transformer it'll do JSON.stringify(transformer.serialize(xxx))
Trader Launchpad
hmmm...ill look into making sure superjson is set up peoperly then, but its using the default setup for packages/api from t3-turbo: I have "superjson": "^1.9.1", in package.json its added to packages/api/src/trpc.ts
Alex / KATT 🐱
doing some tests now to ensure it's not on our end
Trader Launchpad
im using "@trpc/client": "^10.1.0", "@trpc/server": "^10.1.0",
Alex / KATT 🐱
yeah it's not on our end, just did a test try upgrading trpc and superjson trpc will give you type errors in later versions if you have setup the client wrong too misconfig of transformers was so common so we made it into a type error if you did
Trader Launchpad
should i use latest or a specifc version? 10.27.1?
Alex / KATT 🐱
latest
Trader Launchpad
ok i changed all packages to ^10.27.1" ran pnpm i changed lesson router back to z.bigint
videoUri: publicProcedure.input(z.bigint()).query(({ ctx, input }) => {
return ctx.prisma.wp_postmeta.findFirst({
where: {
post_id: input, // Change the post_id value here
meta_key: "app_video_uri", // Replace "video_uri" with the actual meta_key for the meta_value you want to retrieve
},
});
})
videoUri: publicProcedure.input(z.bigint()).query(({ ctx, input }) => {
return ctx.prisma.wp_postmeta.findFirst({
where: {
post_id: input, // Change the post_id value here
meta_key: "app_video_uri", // Replace "video_uri" with the actual meta_key for the meta_value you want to retrieve
},
});
})
i changed the component query to what you suggested:
const { data: videoData, error } = trpc.lesson.videoUri.useQuery(
currentItem?.ID as string,
{
enabled: !!currentItem?.ID,
},
);
const { data: videoData, error } = trpc.lesson.videoUri.useQuery(
currentItem?.ID as string,
{
enabled: !!currentItem?.ID,
},
);
i am getting inline error:
Conversion of type 'bigint | undefined' to type 'string' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first.
Type 'bigint' is not comparable to type 'string'.
Conversion of type 'bigint | undefined' to type 'string' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first.
Type 'bigint' is not comparable to type 'string'.
and console error:
TypeError: Do not know how to serialize a BigInt
TypeError: Do not know how to serialize a BigInt
if I change component query to:
const { data: videoData, error } = trpc.lesson.videoUri.useQuery(
currentItem?.ID as bigint,
{
enabled: !!currentItem?.ID,
},
);
const { data: videoData, error } = trpc.lesson.videoUri.useQuery(
currentItem?.ID as bigint,
{
enabled: !!currentItem?.ID,
},
);
the inline error goes away but i am still getting the serialization console error if i change to:
const { data: videoData, error } = trpc.lesson.videoUri.useQuery(
currentItem?.ID,
{
enabled: !!currentItem?.ID,
},
);
const { data: videoData, error } = trpc.lesson.videoUri.useQuery(
currentItem?.ID,
{
enabled: !!currentItem?.ID,
},
);
I get inline error:
No overload matches this call.
Overload 1 of 2, '(input: bigint, opts: DefinedUseTRPCQueryOptions<"lesson.videoUri", bigint, wp_postmeta | null, wp_postmeta | null, TRPCClientErrorLike<BuildProcedure<"query", { _config: RootConfig<{ ctx: { auth: SignedInAuthObject | SignedOutAuthObject; prisma: PrismaClient<...>; }; meta: object; errorShape: DefaultErrorShape; transformer: typeof SuperJSON; }>; ... 5 more ...; _output_out: typeof unsetMarker; }, wp_postmeta | null>>>): DefinedUseTRPCQueryResult<...>', gave the following error.
Argument of type 'bigint | undefined' is not assignable to parameter of type 'bigint'.
Type 'undefined' is not assignable to type 'bigint'.
Overload 2 of 2, '(input: bigint, opts?: UseTRPCQueryOptions<"lesson.videoUri", bigint, wp_postmeta | null, wp_postmeta | null, TRPCClientErrorLike<BuildProcedure<"query", { _config: RootConfig<{ ctx: { auth: SignedInAuthObject | SignedOutAuthObject; prisma: PrismaClient<...>; }; meta: object; errorShape: DefaultErrorShape; transformer: typeof SuperJSON; }>; ... 5 more ...; _output_out: typeof unsetMarker; }, wp_postmeta | null>>> | undefined): UseTRPCQueryResult<...>', gave the following error.
Argument of type 'bigint | undefined' is not assignable to parameter of type 'bigint'.
Type 'undefined' is not assignable to type 'bigint'.ts(2769)
No overload matches this call.
Overload 1 of 2, '(input: bigint, opts: DefinedUseTRPCQueryOptions<"lesson.videoUri", bigint, wp_postmeta | null, wp_postmeta | null, TRPCClientErrorLike<BuildProcedure<"query", { _config: RootConfig<{ ctx: { auth: SignedInAuthObject | SignedOutAuthObject; prisma: PrismaClient<...>; }; meta: object; errorShape: DefaultErrorShape; transformer: typeof SuperJSON; }>; ... 5 more ...; _output_out: typeof unsetMarker; }, wp_postmeta | null>>>): DefinedUseTRPCQueryResult<...>', gave the following error.
Argument of type 'bigint | undefined' is not assignable to parameter of type 'bigint'.
Type 'undefined' is not assignable to type 'bigint'.
Overload 2 of 2, '(input: bigint, opts?: UseTRPCQueryOptions<"lesson.videoUri", bigint, wp_postmeta | null, wp_postmeta | null, TRPCClientErrorLike<BuildProcedure<"query", { _config: RootConfig<{ ctx: { auth: SignedInAuthObject | SignedOutAuthObject; prisma: PrismaClient<...>; }; meta: object; errorShape: DefaultErrorShape; transformer: typeof SuperJSON; }>; ... 5 more ...; _output_out: typeof unsetMarker; }, wp_postmeta | null>>> | undefined): UseTRPCQueryResult<...>', gave the following error.
Argument of type 'bigint | undefined' is not assignable to parameter of type 'bigint'.
Type 'undefined' is not assignable to type 'bigint'.ts(2769)
Trader Launchpad
as an update I added:
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(BigInt.prototype as any).toJSON = function () {
return Number(this);
};
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(BigInt.prototype as any).toJSON = function () {
return Number(this);
};
to the top of my coursecomponent file and it seems to have fixed the error Reference: https://github.com/prisma/studio/issues/614#issuecomment-1186637811
Alex / KATT 🐱
Might be that react native doesn't support BigInt too
Trader Launchpad
i accepted a solution to my og problem that this thread was titled after. I am still trying to address the issue with BigInts