How do i fetch data from a TRPC endpoint in getStaticProps and getServerSideProps?

currently trying to do it like this
export async function getServerSideProps(context: any) {
const { params, req, res } = context;
const slug = params.slug;
console.log("parandsfjknvedikv", slug);
const { data } = api.profile.getUserByUsername.useQuery({
username: "0xdead",
});
console.log("data", data);

return {
props: {
hello: data,
},
};
}
export async function getServerSideProps(context: any) {
const { params, req, res } = context;
const slug = params.slug;
console.log("parandsfjknvedikv", slug);
const { data } = api.profile.getUserByUsername.useQuery({
username: "0xdead",
});
console.log("data", data);

return {
props: {
hello: data,
},
};
}
throwing an error about how i cant use the useQuery hook in getServerSideProps which makes sense yeah? so how do i do that?
57 Replies
Brendonovich
Brendonovich11mo ago
Server-Side Helpers | tRPC
The server-side helpers provides you with a set of helper functions that you can use to prefetch queries on the server. This is useful for SSG, but also for SSR if you opt not to use ssr: true.
Mj
Mj11mo ago
appreciate the help. after implementing SSH, im getting this error
Mj
Mj11mo ago
Brendonovich
Brendonovich11mo ago
what are you passing to router
Mj
Mj11mo ago
my profile router
Brendonovich
Brendonovich11mo ago
you probably wanna pass in your root router yea idk what the behaviour is if you pass in a sub router
Mj
Mj11mo ago
ohh from root i thought i can pass it in directly
Brendonovich
Brendonovich11mo ago
u might be able to and then remove the profle. part but idk if trpc will use the correct cache key in that case
Mj
Mj11mo ago
working now. appreciate your help taking theos t3 course. do you have another t3 recommendation after this? trying to get very familiar with the stack
Brendonovich
Brendonovich11mo ago
i haven't built anything with t3 haha ¯\_(ツ)_/¯ i just know a lot about the individual parts since we basically recrerated trpc in rust
Mj
Mj11mo ago
oh wow. yet you know trpc so much hahaha okay. just realised im talking to a senior
Brendonovich
Brendonovich11mo ago
and i know way more than i should about prisma bc i made prisma client rust
Mj
Mj11mo ago
nice. tried to pick up rust, was too difficult so i ended up learning golang
Brendonovich
Brendonovich11mo ago
nooooo another person lost to go
Mj
Mj11mo ago
@brendonovich Clerk is really stressing me out. Can you help with something please?
Brendonovich
Brendonovich11mo ago
Idk much about Clerk but ask away
Mj
Mj11mo ago
so here's whats happening basically. following theos t3 tut, i had been using 1 github and 1 gmail account to sign into my app. tried to sign up as a new user now and it always defaults to my github account. i go through all of the process of selecting the gmail account to sign in with, authorize it then it redirects me to my apps homepage with my github logged in. jwt token is clearing from the browser so i know my initial signout is actually signing me out lmfao just figured out whats happening the new email im signing into is the email i used for the github account so it's seeing them as 1 user which is weird
Mj
Mj11mo ago
Brendonovich
Brendonovich11mo ago
ah yea i thought that might be it haha
Mj
Mj11mo ago
hello @brendonovich sorry for the occasional tags but kindly help out please. following react query docs i'm finding it very difficult to understand or implement optimistic updates on my project
Brendonovich
Brendonovich11mo ago
Basically when a mutation is triggered you manually update a query’s data, and if the query fails you roll it back
Mj
Mj11mo ago
yes ser i know exactly thats whats happening, just dont know how from the docs. i apologize if my questions are basic lol. i'm very junior will post a screenshot of where i'm at now Here's my code so far
const {mutate} = api.posts.create.useMutation({
// When mutate is called:
onMutate: async (newPost) => {
// Cancel any outgoing refetches
// (so they don't overwrite our optimistic update)
await queryClient.cancelQueries({ queryKey: ['posts'] })

// Snapshot the previous value
const previousPosts = queryClient.getQueryData(['posts'])

// Optimistically update to the new value
queryClient.setQueryData(['posts'], (old) => [...old, newPost])

// Return a context object with the snapshotted value
return { previousPosts }
},
// If the mutation fails,
// use the context returned from onMutate to roll back
onError: (err, newPost, context) => {
queryClient.setQueryData(['post'], context.previousPosts)
},
// Always refetch after error or success:
onSettled: () => {
queryClient.invalidateQueries({ queryKey: ['posts'] })
},
})
const {mutate} = api.posts.create.useMutation({
// When mutate is called:
onMutate: async (newPost) => {
// Cancel any outgoing refetches
// (so they don't overwrite our optimistic update)
await queryClient.cancelQueries({ queryKey: ['posts'] })

// Snapshot the previous value
const previousPosts = queryClient.getQueryData(['posts'])

// Optimistically update to the new value
queryClient.setQueryData(['posts'], (old) => [...old, newPost])

// Return a context object with the snapshotted value
return { previousPosts }
},
// If the mutation fails,
// use the context returned from onMutate to roll back
onError: (err, newPost, context) => {
queryClient.setQueryData(['post'], context.previousPosts)
},
// Always refetch after error or success:
onSettled: () => {
queryClient.invalidateQueries({ queryKey: ['posts'] })
},
})
Im confused on the following.
queryClient.setQueryData(['posts'], (old) => [...old, newPost])
queryClient.setQueryData(['posts'], (old) => [...old, newPost])
above is returning this error Type 'unknown' must have a 'Symbol.iterator' method that returns an iterator.ts(2488)
queryClient.setQueryData(['post'], context.previousPosts)
queryClient.setQueryData(['post'], context.previousPosts)
this is also returning this error 'context' is possibly 'undefined'.ts(18048) where is the context coming from?
queryClient.setQueryData(['posts'], (old) => [...old, newPost])
queryClient.setQueryData(['posts'], (old) => [...old, newPost])
i'd like to understand exactly what this is doing. especially how to implement the new post object because my post object should contain, createAt, title, and content and i should also be getting an author object from clerk so i'd get the profileImageUrl from it. how do i set those to a temporary object in the newpost?? kindly help please
Brendonovich
Brendonovich11mo ago
Type 'unknown' must have a 'Symbol.iterator' method that returns an iterator.ts(2488)
queryClient won't know about the types of your tRPC router since it's just a React Query thing, it has no relation to tRPC. tRPC has useContext that provides properly typed equivalents of each React Query function: https://trpc.io/docs/client/react/useContext#helpers. Would recommend using these instead of using queryClient.*
'context' is possibly 'undefined'.ts(18048)
I'd just return early if context is undefined
how do i set those to a temporary object in the newpost??
newPost contains all the data that's passed into the mutation, you can customise it as you wish. Idk what you'd do for the clerk stuff but yea u can chuck whatever in there
useContext | tRPC
useContext is a hook that gives you access to helpers that let you manage the cached data of the queries you execute via @trpc/react-query. These helpers are actually thin wrappers around @tanstack/react-query's queryClient methods. If you want more in-depth information about options and usage patterns for useContext helpers than what we provide...
Mj
Mj11mo ago
same error sir
Mj
Mj11mo ago
here's my code
const {mutate, isLoading: isPosting} = api.posts.create.useMutation({
onMutate: async (newPost) => {
// Cancel any outgoing refetches
// (so they don't overwrite our optimistic update)
await utils.posts.getAll.cancel();
//snapshot a previous value
const posts = api.posts.getAll.useQuery();
const previousPosts = posts.data;
utils.posts.getAll.setData(undefined, (old) => {
return [...old, newPost];
});
},

onSettled: () =>
api.posts.getAll.useQuery().refetch(),
});
const {mutate, isLoading: isPosting} = api.posts.create.useMutation({
onMutate: async (newPost) => {
// Cancel any outgoing refetches
// (so they don't overwrite our optimistic update)
await utils.posts.getAll.cancel();
//snapshot a previous value
const posts = api.posts.getAll.useQuery();
const previousPosts = posts.data;
utils.posts.getAll.setData(undefined, (old) => {
return [...old, newPost];
});
},

onSettled: () =>
api.posts.getAll.useQuery().refetch(),
});
import { api } from "~/utils/api"; const utils = api.useContext(); heres how i got both utils and api. can you explain their differences please?
Brendonovich
Brendonovich11mo ago
Ah again just return early if old is undefined
barry
barry11mo ago
const posts = api.posts.getAll.useQuery(); You can't just call it in the onMutate afaik?
Mj
Mj11mo ago
kindly explain @thatbarryguy do you mean i should just do api.post.getAll.GetData()?
barry
barry11mo ago
🤷 I just know you can't call a hook like that
Mj
Mj11mo ago
whats the better way to call it? #noob can you help with the problem im facing? @thatbarryguy
Brendonovich
Brendonovich11mo ago
Barry is right in that you can’t use useQuery there - you’ve incorrectly translated the previous code you had You should be using getData instead of useQuery since that’s the stated equivalent to getQueryData
Mj
Mj11mo ago
const addPost = api.posts.create.useMutation({
onMutate: async (newPost) => {
// optimistically update the cache
await utils.posts.getAll.cancel(); // Cancel any outgoing refetches


// Snapshot the previous value
const previousPosts = utils.posts.getAll.getData();

const fakeId = Math.random().toString();

const optimisticPost: OptimisticPost = {
id: fakeId,
title: new Date(),
content: newPost.content,
authorId: "your-user-id", // Replace this with the actual user ID
author: {
id: "your-user-id",
username: "your-username",
profileImageUrl: "your-profile-image-url",
name: "your-name",
},
};

// Optimistically update with the new value by setting the cache
utils.posts.getAll.setData(undefined, (old) => {
return [...(old ?? []), optimisticPost];
});

// Return a context object with the snapshotted value
return { previousPosts };
},

onError: (_err, _newPost, context) => {
// If the mutation fails, use the context returned from onMutate to roll back!
utils.posts.getAll.setData(undefined, (old) => context?.previousPosts ?? old);
},

onSettled: async () => {
// make sure to invalidate when done
await utils.post.all.invalidate();
},
});
const addPost = api.posts.create.useMutation({
onMutate: async (newPost) => {
// optimistically update the cache
await utils.posts.getAll.cancel(); // Cancel any outgoing refetches


// Snapshot the previous value
const previousPosts = utils.posts.getAll.getData();

const fakeId = Math.random().toString();

const optimisticPost: OptimisticPost = {
id: fakeId,
title: new Date(),
content: newPost.content,
authorId: "your-user-id", // Replace this with the actual user ID
author: {
id: "your-user-id",
username: "your-username",
profileImageUrl: "your-profile-image-url",
name: "your-name",
},
};

// Optimistically update with the new value by setting the cache
utils.posts.getAll.setData(undefined, (old) => {
return [...(old ?? []), optimisticPost];
});

// Return a context object with the snapshotted value
return { previousPosts };
},

onError: (_err, _newPost, context) => {
// If the mutation fails, use the context returned from onMutate to roll back!
utils.posts.getAll.setData(undefined, (old) => context?.previousPosts ?? old);
},

onSettled: async () => {
// make sure to invalidate when done
await utils.post.all.invalidate();
},
});
Brendonovich
Brendonovich11mo ago
Seems good
Mj
Mj11mo ago
error
Argument of type '(old: { post: GetResult<{ id: string; title: Date; content: string | null; authorId: string; }, unknown> & {}; author: { id: string; username: string | null; profileImageUrl: string; name: string; }; }[] | undefined) => ({ ...; } | { ...; })[]' is not assignable to parameter of type 'Updater<{ post: GetResult<{ id: string; title: Date; content: string | null; authorId: string; }, unknown> & {}; author: { id: string; username: string | null; profileImageUrl: string; name: string; }; }[] | undefined, { ...; }[] | undefined>'.
Argument of type '(old: { post: GetResult<{ id: string; title: Date; content: string | null; authorId: string; }, unknown> & {}; author: { id: string; username: string | null; profileImageUrl: string; name: string; }; }[] | undefined) => ({ ...; } | { ...; })[]' is not assignable to parameter of type 'Updater<{ post: GetResult<{ id: string; title: Date; content: string | null; authorId: string; }, unknown> & {}; author: { id: string; username: string | null; profileImageUrl: string; name: string; }; }[] | undefined, { ...; }[] | undefined>'.
Brendonovich
Brendonovich11mo ago
It’s hard to say since I don’t know where the error is occurring but it looks like one of your functions is returning the wrong value
Mj
Mj11mo ago
utils.posts.getAll.setData(undefined, (old) => {
return [...(old ?? []), optimisticPost];
});
utils.posts.getAll.setData(undefined, (old) => {
return [...(old ?? []), optimisticPost];
});
this is where the error is coming from. if you dont mind ser, i could provide the repo link.
Brendonovich
Brendonovich11mo ago
({ …; } | { …; })[] hints to me that the type of optimisticPost doesn’t match the rest of the items in old The array you’re returning could have 2 different types of elements I see you have an OptimisticPost type, which probably isn’t a good idea since you need to use the element type of the array returned from that query tRPC has utilities to extract return types from queries which you could use to give optimisticPost a correct type based on the return type of the query
Mj
Mj11mo ago
thank you for your help so far ser yes i did that and assigned it to optimisticPost
const { mutate, isLoading: isPosting } = api.posts.create.useMutation({
onMutate: async (newPost) => {
// optimistically update the cache
await utils.posts.getAll.cancel(); // Cancel any outgoing refetches

// Snapshot the previous value
const previousPosts = utils.posts.getAll.getData();

const fakeId = Math.random().toString();

const optimisticPost: optimisticPost = [
{
...newPost,
id: fakeId,
title: new Date(),
content: "This is the content of the post",
authorId: "user_2TAzpA7V1ymtk5HFAnr5IbkFd0W",
},
];

// Optimistically update with the new value by setting the cache
utils.posts.getAll.setData(undefined, (old: any) => {
return [...old, ...optimisticPost];
});

// Return a context object with the snapshotted value
return { previousPosts };
},

onError: (_err, _newPost, context) => {
// If the mutation fails, use the context returned from onMutate to roll back!
utils.posts.getAll.setData(
undefined,
(old) => context?.previousPosts ?? old
);
},

onSettled: async () => {
// invalidate when done
await utils.posts.getAll.invalidate();
},
});
const { mutate, isLoading: isPosting } = api.posts.create.useMutation({
onMutate: async (newPost) => {
// optimistically update the cache
await utils.posts.getAll.cancel(); // Cancel any outgoing refetches

// Snapshot the previous value
const previousPosts = utils.posts.getAll.getData();

const fakeId = Math.random().toString();

const optimisticPost: optimisticPost = [
{
...newPost,
id: fakeId,
title: new Date(),
content: "This is the content of the post",
authorId: "user_2TAzpA7V1ymtk5HFAnr5IbkFd0W",
},
];

// Optimistically update with the new value by setting the cache
utils.posts.getAll.setData(undefined, (old: any) => {
return [...old, ...optimisticPost];
});

// Return a context object with the snapshotted value
return { previousPosts };
},

onError: (_err, _newPost, context) => {
// If the mutation fails, use the context returned from onMutate to roll back!
utils.posts.getAll.setData(
undefined,
(old) => context?.previousPosts ?? old
);
},

onSettled: async () => {
// invalidate when done
await utils.posts.getAll.invalidate();
},
});
now i dont get any error in the console but it fetched every second so my UI glitches. i wonder if there's anything i did wrong here to cause that i know i could just invalidate after every post but i dont wanna give up on learning optimistic updates.
barry
barry11mo ago
onSettled will keep running the invalidate no?
Mj
Mj11mo ago
no thats not it. problem persists
barry
barry11mo ago
I’m on phone can’t clone
Mj
Mj11mo ago
oh i give up
Brendonovich
Brendonovich11mo ago
you're defining components within components don't do that
Brendonovich
Brendonovich11mo ago
wut
Mj
Mj11mo ago
haha thanks for checking it out ser fixed that any feedback? probably all the debugging, didnt realise
Brendonovich
Brendonovich11mo ago
i suspect the components in components may have caused the refetching but i'm not sure
Mj
Mj11mo ago
i only have loader there i know you suggested i used trpc helper wrappers to implement it but will using react query queryClient directly work? do i have to do it the trpc way?
Brendonovich
Brendonovich11mo ago
it'll probably work but i'd think you'd be doing yourself a disservice not having the types available
Mj
Mj11mo ago
i know the major problem right now is the fact that i'm not getting the right type for the
old
old
instead of setting it to any. here's someone that successfully implemented it.
const addTodo = api.todo.create.useMutation({
onMutate: async ({ content }) => {
setTodo("");

await utils.todo.findAll.cancel();


const previousTodos = utils.todo.findAll.getData();


utils.todo.findAll.setData(
undefined,
(oldQueryData: TodoWithUser[] | undefined) =>
[
...(oldQueryData ?? []),
{
author: {
name: session?.user?.name,
id: session?.user?.id,
},
content,
done: false,
createdAt: new Date(),
updatedAt: new Date(),
},
] as TodoWithUser[]
);


return { previousTodos };
},
onError: (err, _newTodo, context) => {

utils.todo.findAll.setData(undefined, context?.previousTodos);

// Show error message
const zodErrMessage =
err.shape?.data.zodError?.fieldErrors.content?.at(0);
toast({
variant: "destructive",
title: "Error while creating todo",
description: zodErrMessage
? zodErrMessage
: "There was an error while saving todo. Please try again later.",
});
},
onSettled: () => {
void utils.todo.findAll.invalidate();
},
});

const handleInputOnChange = useCallback(
(event: React.ChangeEvent<HTMLInputElement>) => {
setTodo(event.target.value);
},
[]
);

const handleAddTodo = useCallback(() => {
addTodo.mutate({
content: todo,
});
}, [addTodo, todo]);

const handleKeyDown = useCallback(
(event: React.KeyboardEvent<HTMLInputElement>) => {
if (event.key === "Enter") {
handleAddTodo();
}
},
[handleAddTodo]
);
const addTodo = api.todo.create.useMutation({
onMutate: async ({ content }) => {
setTodo("");

await utils.todo.findAll.cancel();


const previousTodos = utils.todo.findAll.getData();


utils.todo.findAll.setData(
undefined,
(oldQueryData: TodoWithUser[] | undefined) =>
[
...(oldQueryData ?? []),
{
author: {
name: session?.user?.name,
id: session?.user?.id,
},
content,
done: false,
createdAt: new Date(),
updatedAt: new Date(),
},
] as TodoWithUser[]
);


return { previousTodos };
},
onError: (err, _newTodo, context) => {

utils.todo.findAll.setData(undefined, context?.previousTodos);

// Show error message
const zodErrMessage =
err.shape?.data.zodError?.fieldErrors.content?.at(0);
toast({
variant: "destructive",
title: "Error while creating todo",
description: zodErrMessage
? zodErrMessage
: "There was an error while saving todo. Please try again later.",
});
},
onSettled: () => {
void utils.todo.findAll.invalidate();
},
});

const handleInputOnChange = useCallback(
(event: React.ChangeEvent<HTMLInputElement>) => {
setTodo(event.target.value);
},
[]
);

const handleAddTodo = useCallback(() => {
addTodo.mutate({
content: todo,
});
}, [addTodo, todo]);

const handleKeyDown = useCallback(
(event: React.KeyboardEvent<HTMLInputElement>) => {
if (event.key === "Enter") {
handleAddTodo();
}
},
[handleAddTodo]
);
Brendonovich
Brendonovich11mo ago
so much useCallback 🤦 not a knock on you, just saying haha
Mj
Mj11mo ago
Here's the type he's using
import { Prisma } from "@prisma/client";

const todoInclude = Prisma.validator<Prisma.TodoInclude>()({
author: {
select: {
name: true,
id: true,
},
},
});

export type TodoWithUser = Prisma.TodoGetPayload<{
include: typeof todoInclude;
}>;
import { Prisma } from "@prisma/client";

const todoInclude = Prisma.validator<Prisma.TodoInclude>()({
author: {
select: {
name: true,
id: true,
},
},
});

export type TodoWithUser = Prisma.TodoGetPayload<{
include: typeof todoInclude;
}>;
oh it's not my code lol. just one i saw on github that's working i was also wondering why he was using the callback hook a lot but what concerns me is his optimistic update query. kindly take a look ser is there a way to generate this type from prisma for my post query?
Brendonovich
Brendonovich11mo ago
i mean it looks pretty normal i don't see why you'd use a prisma type when u can use the types from your trpc router
Mj
Mj11mo ago
you mean using
type optimisticUpdates = RouterOptions[".."][".."]
type optimisticUpdates = RouterOptions[".."][".."]
something like that right? i did that too
Brendonovich
Brendonovich11mo ago
yea are u having a problem with types or with the runtime logic?
Mj
Mj11mo ago
this is my oldqueryData type
(parameter) oldQueryData: {
post: GetResult<{
id: string;
title: Date;
content: string | null;
authorId: string;
}, unknown> & {};
author: {
id: string;
username: string | null;
profileImageUrl: string;
name: string;
};
}[] | undefined
(parameter) oldQueryData: {
post: GetResult<{
id: string;
title: Date;
content: string | null;
authorId: string;
}, unknown> & {};
author: {
id: string;
username: string | null;
profileImageUrl: string;
name: string;
};
}[] | undefined
you know in optimistic updates you have to recreate what is happening in the object basically with this type this is my sample object to pass into the setData method but its throwing an error
[
{
"post": {
"id": "1",
"title": "2023-07-05T12:34:56.789Z", //dont worry about this tite, its supposed to be createdAt. it's a date object.
"content": "This is the content of the post",
"authorId": "user_2TAzpA7V1ymtk5HFAnr5IbkFd0W"
},
"author": {
"id": "user_2TAzpA7V1ymtk5HFAnr5IbkFd0W",
"username": "john_doe",
"profileImageUrl": "https://clerk.com/john_doe.jpg",
"name": "Test Name"
}
},
]
[
{
"post": {
"id": "1",
"title": "2023-07-05T12:34:56.789Z", //dont worry about this tite, its supposed to be createdAt. it's a date object.
"content": "This is the content of the post",
"authorId": "user_2TAzpA7V1ymtk5HFAnr5IbkFd0W"
},
"author": {
"id": "user_2TAzpA7V1ymtk5HFAnr5IbkFd0W",
"username": "john_doe",
"profileImageUrl": "https://clerk.com/john_doe.jpg",
"name": "Test Name"
}
},
]
Brendonovich
Brendonovich11mo ago
what's the error? and is it throwing an error or is typescript giving you an error?
Mj
Mj11mo ago
hello sir. how do i pass data into a trpc useQuery wrapper? i'm trying to pass in refetchOnWindowFocus to my useQuery nevermind ser. got it noticed in trpc we pass a lot of undefined in our functions, why?