In this chapter we're gonna go ahead and set up the agents entity in our project. This will include adding the agents schema, module, TRPC procedures, some client pages and we're going to create reusable loading and error states. Let's start with the agent schema. First things first, ensure that you're on your main branch here and feel free to synchronize changes if you haven't. And make sure you have no active numbers here, meaning everything was merged, everything is up to date.
So what I want to do now is run npm install nano ID legacy peer depths. Great, now let's go ahead and run our app now let's go inside of source database schema inside of here we have all the tables added by BetterOut. So what we are going to do now is import NanoID from NanoID and we are going to create our table. So let's go ahead and export const agents pgTable agents. The ID is going to be a type of text with the column name ID.
It will be a primary key and we are going to add a default function here to assign a nano ID. The reason I'm using nano ID is because they are shorter and much more readable than uuids. Let's go ahead and set the name field to use the name column name. And it's going to be required. User ID is going to be a text field.
User underscore ID will be the column name, it will be required, and it's going to be a reference to our existing user table, specifically targeting the ID field. So we are referring to this user table right here, this ID field. And we are going to add a rule. If the user is deleted, we are going to cascade, meaning deleting this agent as well. Let's add instructions to be a type of text with a column name instructions and let's just make it required and we are then gonna have created at which will be a type of timestamp created underscore at required and default now Copy and paste this and change it to updated add and change this to updated add as well.
There we go. We have our new agents table. So everything above here is better out and everything starting from the agents will be our tables. Great. Now that we have our new agent schema, let's go ahead and let's run database push.
So npm run database push. And there we go. Changes applied. So now you have two options. You can either go on Neon and look at the table or you can run database studio.
So whichever one you prefer. You can go to local.drizzle.studio to find the studio here, or you can go to your Neon console here and go inside of the tables and you will find the exact same studio here. So now you have the agents and in here you have ID, name, user ID, instructions, created at, and updated at. And the last one is a repeated user which is basically just the relation which will be loaded once we add the user ID. Perfect.
So we added the agents schema. Now what I want to do is I want to add the agents module. So let's go ahead and close everything. Let's go inside of source modules. And let's add agents here.
And then inside of here, let's add a server folder. And let's add procedures dot ts. Inside procedures dot ts inside of the procedures here I want to import create the RPC router from the RPC in it and base procedure from the RPC in it. Let's export const agents router, and let's make it create the RPC router. And let's add get many to be base procedure query, and it's going to be a simple asynchronous function.
And inside of here, we're just going to get the data using a weight database from at slash DB. And let's go ahead and select From Agents. So make sure to import database and agents. And we don't need to add any other query because we are not querying right now by user, we are not querying by pagination, we are just loading all data. So let's go ahead and return this.
Now that we have that, let's go inside of drpc routers underscore app. And let's go ahead and prepare a bit. So let's import agents router from modules agent server procedures. And let's remove the Hello procedure and add agents to be agents router. And you can remove base procedure and Zod from here, like this.
Now, let's go inside of Home in the Modules folder, UI Views, Home View, and in here, let's clear it up entirely and let's just say home view here. You can even remove the class names. Remove these two imports and just leave it like this. This way, the component is no longer erroring. Great.
So we now added the procedures. Now let's go ahead and finish the module by adding the agent pages. So let me just go ahead and start my app here, npm run dev. Let's wait a second for this to load. There we go.
So we just have home view here. And now let's go ahead and actually run this. Let me just move this here. Bottom right. There we go.
So it doesn't cause any issues. And now let's go ahead and develop the agents page. So we're going to go inside of source app folder dashboard, and we're going to create the agents folder, that inside of here page, the s x, Then let's go ahead and simply do a default export here and write agents, like this. So now when you go ahead and click on agents here, you will see the text agents. Now let's go ahead, go inside of modules, agents, and create the UI folder inside and create a views folder inside of UI and finally in here create agents dash view TSX market as use client import use the RPC from the RPC client import use query from 10 stack react query Now let's export const agents view here.
Let's go ahead and prepare TRPC using use query, my apologies, use TRPC. And let's get the data by using use query here, TRPC agents get many query options like this. Use query here, PRPC, agents, getMany, queryOptions, like this. Let's go ahead and return a div, JSON stringify data, null, and two. So null and two is basically used to format the JSON stringification in a nice way so it's easier to read.
And let's also do the following. Is loading. If is loading. Let's go ahead and return a div loading. And if is error, Let's return a div error.
Like this. And I just have to wrap it this in parentheses, because I can't look at it like that. Okay, great. So now let's go to the page here inside of our dashboard agents page. And let's go ahead and directly return the agents view like this.
And there we go. You're now able to click add agents, right? And you will just see an empty array. So what you can do is you can go inside of your either Neon console or Drizzle Studio, whatever you have up and running. Go inside of agents and click add record.
For ID you can just go ahead and type whatever you want because it's a type of text. But before you do this, let's just discard this. Go to your users and just pick at least one user here. Just pick an ID for the user, copy it, go inside of agents, click add record, write whatever you want for the ID for the agent, the name can be first agent, and then user ID, paste the ID that you've added. In the instructions, add you are an agent.
It really doesn't matter. And create it and update it at our default. So you can click Save One Change. And there we go. That will create an agent with a relation to the user.
In case you fail at doing this, no problem at all. I'm literally just adding one record, simply so we can see something being loaded here. That's the only reason why I'm doing this. If you are failing at doing it, don't worry. Great.
So now that we have this, let's go ahead and spice it up a little bit. So let's go inside of the agent's view here. Let's go inside of getMany. And how about I go here, And I purposely make it slower by using await new promise, resolve, set timeout, resolve 5, 000. So I'm purposely adding five seconds here.
So let's refresh this. And now you can see our loading state being active here for five seconds before it loads. Now let's comment this out. And instead, let's throw a new PRPC error here with the code bad request, for example. Let's refresh this as well.
So it's going to load, it's going to load for a couple of times until it eventually hits the error, I believe. And there we go. So after a couple of retries, it shows the error state. Perfect. So we now have little shortcuts to do both of them.
So what I want to do now is the following. I want to build our error state and our loading state, which we are going to reuse throughout the project. So let's go inside of source components and create loading state dsx and do the following. Import loader2 icon from Lucid React. Create an interface props with a title of string and description of string.
Export loading state like this. Destructure the props here so you can obtain the title and the description. And inside of here, return a div. The div will have a class name of flex, flex call, Items center, justify center, gap, y, 6, bg, background, rounded, large, padding of 10, and shadow sm. Inside of here add a Loader2 icon which we have imported above and give it a class name of Size6 and AnimateSpin.
And let's also give it a text primary. Let me just fix the typo in my AnimateSpin class name here. Below this, open up a div with a class name flex, flex column, gap y2, and text center. Inside of this div, you will have a heading 6 which will render the title, and below it a paragraph which will render the description. Go ahead and give the heading a class name of text large and font medium and give the paragraph a class name, text small.
There we go. Now let's head back inside of our agents view, inside of modules agents UI views agents view. Instead of returning this, we are now going to return our loading state, like that. Let's go ahead and give this a title of loading agents and a description of this may take a few seconds like this. So now let's go back inside of our procedures and let's enable our five second timeout.
And there we go. Now we have a nice loading state which we can reuse throughout our project. Perfect. So now what I want to do is the following. I want to copy the loading state, paste it here and rename it error state.
Inside of here, I will import the alert. Let me just see which one is the proper alert circle icon. Let's use this one. And this will be called error state. And it's simply going to load the alert circle icon, It will remove animate spin and it will use a color text red 500.
I like that. Go back inside of the agents view and in here use the error state. Give it a title of failed to load agents and a description of something went wrong. Or maybe let's do, please try again later, and let's do error loading agents. And in here, we can remove these three dots like this so now How about we enable the error and remove this so it errors faster.
So let's refresh now. So it's loading, it's loading, it's loading, and it will eventually hit the error state displaying the error. There we go. So what you've just learned is how to use DRPC with useQuery in the most normal way everyone is used to using it. And if you want to, you can complete the entire project like this.
There is absolutely nothing wrong with doing it like this. If you prefer having your error states here, your loading states here, that's perfectly fine. But what I'm going to show you now is how to do it by leveraging server components. Because right now we are using this agents view as the fetcher right but we have this out my apologies whoops dashboard agents page which is a server component which has closer access to the database than this which is essentially a fetch API call right so what if we could already fetch the data in the server component and then immediately provide it to the cache here. What if we could do that?
Well, good news is we can do exactly that. So let's turn this into an asynchronous component, like this, And let's go ahead and define the following, const queryClient. And let's do get queryClient from trpcServer. So you already know that we are going to be using the server utils for this one. So we get the queryClient here.
And then what we do is avoid query client, refetch query. And inside we do trpc, which you can also import from the server. It's important that you import both from trpc server, not from anywhere else and in here do .agents getMany and simply do queryOptions here just like that. So what we've done now is we used a server components to pre-fetch the agents getMany But we are still not done. What we have to do now is we have to wrap this agentsView component in a hydration boundary from 10-stack ReactQuery.
So let's do this and let's pass in the state here to be dehydrate from 10 stack react query and pass in the query client like this. So what we have done now is the following flow. The first component that will load when the user clicks on the agents here will be this page, this server component. This server component will then refetch the RPC agents getMany and it will hydrate through hydration boundary the react query or 10 stack react query cache so then when the agents view finally loads it will already have the data inside reducing the load time. But in order to do that properly, we need to not use useQuery.
Instead, we have to use useSuspenseQuery. Like this. And we have to modify a couple of things. So since we're using useSuspenseQuery, it makes no sense to use the error and loading here. Why?
Well, let's look at the difference. I'm just going to Ctrl-Z back. So when I hover over data here, you can see that in here, it can be undefined, right? Because there is a state where this data is not loaded yet. That's why we handle isLoading.
But when you switch to useSuspenseQuery and hover over the data, it will never be undefined because useSuspenseQuery has already fetched data inside of it, already resolved data. So That's why it doesn't make sense to use these here at all. So you can remove them. And you can completely rely on the data here. Instead, what we're going to do is the following.
We're going to go ahead and wrap the agents view inside of suspense from React. And we're going to give it a fallback. And let's just keep this thing simple. So you have two ways of doing it. You can add loading state here if you want to and give it a title of Loading Agents and a description here of this may take a few seconds.
If you want to, you can do it like that. Or you can export const AgentsViewLoading and then in here return the loading state and simply repeat these two values like this and then in here you can do a simpler version, agents view loading. And you import all of them from the same place, agents view and agents view loading, whichever version you prefer and it's a self-closing tag great so now go inside of your procedures for the agents router whoops and enable this five second time out So now do a hard refresh here and you can see it is still working but this time it's leveraging suspense. Great! So how do we handle errors here?
Well, we need an error boundary. Now, Next.js by itself has an error boundary. So that means that you could go inside of the agent's page here and you could create an error.tsx, mark it as use client, export. I think you need to do a default export as well, or you can just call it error page. Make sure to not call it error because that's a reserved file name.
And you could just return the error state here, I believe, with a title error loading agents and a description here, Something Went Wrong. And I haven't tested this, But I think it should work. So let me try and enable the error now, and let me do a hard refresh here. And I'm just going to see if this is working. So let's wait a few seconds.
And there we go. Yeah. So Just like that, we handled the error state. If you want to, you can handle error states like that, because Next.js offers this native error boundary the same way it actually offers a loading boundary. But there is another way of doing it, and that is by using React error boundary, dash dash legacy here, deps, like this.
So in here, what you can do is inside of suspense, add error boundary. Make sure to not import it yet. And what you can do is copy this error state from here, go to agents view, export cons agents view error, return the error state here, import the error state like this, keep them all together, go here in the page, you will import error boundary from react error boundary like this and give it a fallback of agents view error. So now you have all three components from the same place like this. And if you're wondering why would I do this over Native Next.js ones, Well, that's because native Next.js error boundary works on an entire page level, which in this specific case makes sense.
But imagine you just wanted it on a very small component in that page. That's where having this kind of error boundary makes more sense. So you can now remove this error.tsx and you can do a hard refresh here. And let me just see. So yeah, there is one issue that might occasionally happen.
I don't know if you've noticed it. It eventually showed this error. But it's very hard to reproduce. For some of you it might happen, for some of you it might not happen. But keep your inspect element open.
And make sure... There we go. You can see what's happening. So this is an infinite loop that it was caught in. And the only time I've noticed it happen is either completely randomly or when you throw an error, Right?
So I'm not sure if it happens with the native error boundary from Next.js, but what I do know... Okay, let me just do a hard refresh here again. Yeah, you can see that when I refresh, sometimes it happens, sometimes it doesn't. So it's not exactly deterministic. And when I say hard refresh, I mean Command Shift R, or basically this, ignoring the cache.
I think you can do that in Chromium browsers, right? It's something relating with the hot reload. I'm not sure if it even translates to production at all. But yeah, sometimes it can happen. So if you get that, don't worry.
I get it as well. And other people do. So there is an open issue on 10-stack query, maximum update that exceeded here. And it's very hard to track why it's happening. You can see that people are having trouble narrowing it down.
There is a specific version they found. So if you want to completely get rid of it, you could technically go inside of the package.json, find the 10-stack query, and downgrade, you know, oops, not this one, and stack react query, right, find this one. And you could technically, you know, downgrade it to this version. And apparently, the issue does not happen there. Because that's the last place they did some...
Actually, it would be 0.3. This would be the version without any issues with infinite hydration error loops, right? Just for your information. Because that's where they introduce some hydration changes. And then you can see a bunch of discussion here.
I think that even I left a comment here, as you can see that I encountered this issue as well and when I use 5.66.3 the error actually goes away. The good news is they seem to be getting to the bottom of it as you can see just yesterday at least of the time I'm recording this video they seem to be having a fix. So if you want to, you can also follow this discussion. So just please don't spam anything here, right? They are doing their best to fix this.
It doesn't help anyone by commenting, I have the same problem, right? Unless you have something of insight, no need to spam the issue, they are obviously fixing it. So if you want to, you can follow this issue along, maximum update that exceeded in 10-stack query. And in here, you can find the preview, pre-release packages, which apparently fix the issue. I have not tested it yet to confirm.
So what you could do is do npm install this add dash dash legacy peer deps and then you would have this type of version inside. It's going to look like this. So that's like a pre-release. But basically, I'm just trying to tell you, yeah, I'm having that maximum update depth error as well, but I did not experience it breaking my app in any way. Right?
It doesn't seem to freeze the entire app. It seems to be somehow controlled. Another thing you can keep an eye on is this issue simply to see if it's closed. If it's closed, it probably means that it might be a good idea to check for a newer version of React Query. Maybe that will be 0.2.
So go ahead and do a little bit of research if you're interested, and maybe they will fix it in the next version. But nevertheless, I developed with this version and I did not find any big problems. It happened very rarely, mostly because we don't really throw that many errors, right? You can see that sometimes it happens, sometimes not. But eventually, this maximum update loop ends and it shows the error.
So yeah, not sure exactly what's going on here, but just to take your mind off ease, it's let's say quote unquote normal, right? At least they are on it, they know it's happening. So you can now remove these two. You can remove this. And you can do a hard refresh just to prove that it's still working.
And I'm not sure if you noticed, but it is significantly faster now. The loading is much, much quicker than it was when we used the use query, right? So a big important thing here is if you're planning on using useSuspenseQuery in this way, you need to make sure that in your server component, in your parent server component, you are doing the proper prefetching here with the proper hydration boundary. Because if you forget to do these things, you will end up with some cryptic errors. So right now it is working.
The reason it's working here is because if it detects, if useSuspenseQuery detects no data, meaning nothing was prefetched, what will happen is it will simply fall back to use query. But this will become a problem with protected routes. And you're going to see that later when we actually implement some protected routes. So I just showed you a bunch of options you can fetch data in your project, a bunch of ways you can show loading states, you can show error states, we even use the native Next.js error boundary. So I hope that gave you a lot of new options and the idea of how this works.
And as I said, if you have a preference for just using useQuery and then handling loading an error here, sure, no problem at all in doing that. This is just an optimized way of fetching your data. It doesn't have that much effect here because we just have this simple data, but later when you have actual data, it will definitely, you know, you will see that it's faster. Perfect. So I think that that was everything I wanted to show you.
We have the agents modules, agent pages, loading state and error state and now let's go ahead and create, review and merge this pull request. So 09 agents setup. So I'm gonna go ahead and click on my branch here, create new branch, 09-agents-setup, confirm you are in your new branch, add all changes here, 09-agents-setup, commit, and publish the branch. Now let's go ahead to our GitHub, let's open the pull request, let's create it, and let's see the review from CodeRabbit. And here we have the summary.
We have introduced an agents dashboard page with server-side data fetching, loading, and error handling. We added reusable loading and error state UI components and we have implemented an agent's database table to store agent information. We have also enabled the DRPC API endpoint for retrieving agent data. Perfect! And we have added some new dependencies nano id and react error boundary.
Perfect! In here we have file by file change summary and in here we have the sequence diagram and these will only become more useful the more we use this type of prefetching, right, because they will be helpful in explaining why and how things are happening. So let's go ahead and go over them a little bit. So It starts with the user visiting dashboard agents on the agents page. The agents page is the server component.
In there, we do a prefetch to agents.getMany. That in turn calls the TRPC server, which fetches all agents from the database. It returns the agent place to the TRPC server, which we then return to the TRPC client, which finally hydrates the agents page view with the agent data. And then we can render that data finally. Here is the agent view, my apologies.
There we go. Perfect. So it clearly shows how we are prefetching through the parent server component. And as per the comments here, it only has comments regarding the fact that this query is obviously not something you would use in production. That's completely fair comment here.
But we will do that later when we actually implement protected procedures so that we can query by a specific user and when we add pagination here. And same thing for actually displaying those elements you can see in here they've created an entire state for us here. Perfect. So let's merge this pull request. Let's confirm merge and once you've merged it you can go ahead back inside of your IDE, select your main branch and synchronize changes.
Click ok. Go inside of your source control here, open the graph and confirm that you have just merged 09 agents setup. There we go. Perfect. Amazing, amazing job and see you in the next chapter.