All right, so in order to complete the data table component I first want to load actual data from my accounts API inside of here. So if you remember, instead of our app folder, let me just zoom in, instead of app dashboard accounts page.tsx right here we created a mock data like this. So let's go ahead and slightly modify this. So first of all I'm gonna remove the type here for data right let's go ahead and leave this to be an error right it doesn't matter for now and let's remove the payment import so I just want you to have import columns from column and make sure you do this make sure you remove that payment type before you continue into doing what we are about to do. So what I want to do now is I want to give my columns inside a proper type.
Which type? Well I specifically want the success format of this API route, the get route. So we have, when I hover over get, there is a possibility of me getting an error and an array of ID and name inside of the data object, right? So I specifically only want this. So I could just copy that and paste it here, right?
That would work, but we don't want that. We wanna reuse it specifically from this endpoint and we can do that. So let's remove inside of accounts columns. Let's remove the export type payments and these two comments and let's go ahead and add the following stuff. So I want us to add infer.
Response type from Hono and I want us to add client from libHono. And now what you can do here is write export type, response type. And we're going to use infer response type and inside of pointy brackets we're gonna write type of client.api.accounts.get Now let me zoom out just so you can see this in one line. But we have an issue here. So this seems fine right?
But when I hover over response type, it tells me that it can either be an error or it can be this. What I want is, I just want one element. I just want to get this, the ID and the name in an object. That's the only thing I want. So how can I do that?
Well, here are some documentation from the Hono 4.3.0 release. Let me scroll up so you can see. So the release four days ago, as of my recording, 4.3.0 added the exact thing we need. So check your package.json just in case and ensure that you have Hono higher than 4.3 at least 4.3.0 right so you're definitely gonna have that if you're watching this tutorial. So what we can do is we can use the following.
We can use status code type. So we can specifically... So this is the exact thing we just made right? And then we can add a comma and specify the status and that way we can only get a success message. So it's important that whenever you throw errors inside of your Hono endpoints make sure that you add a comma and an error code.
That way the Hono typing knows all right this is only the type if we throw an error. So now inside of my columns here what I can do is I can add a comma and add 200. And then when I hover I only get the success format. Let's try it again. When I remove this then I can have either an error or the data.
So that's how it works. So let's add 200 here but we are not done yet. We now specify the data, more specifically the first item in the array. That is our type. ID and name.
Let's copy that and let's put it here instead. There we go. That is our type. Perfect. Now let's go ahead and let's remove all of these things which we don't need so we can remove this.
Accessor key status and header status. You can remove that and leave the email simply because we have this cool sorting here already done so we can reuse that, right? So instead of email this is going to be name because well that's the only field we have besides ID and let's write name here We don't have to modify this at all and we're not gonna have amount So let's leave that as it is great now that we have that let's go ahead inside of page.psx right here and well we can leave this as it is what we're gonna do instead is we are gonna remove the mock data so we no longer need that let's go ahead and give this an empty array there we go so no errors are being thrown now but what I want to do now is I want to use my hook which I believe we already created in feature accounts API use get accounts so we have the hook which will call all accounts so we can use that right here let's go ahead and find and write const accounts query to be use get accounts and let's add the import from features.
I'm gonna move it along my new account features here and then inside of here let's define const accounts to be accounts query.data or an empty array like that and let's go ahead and let's add accounts inside. So now if you have some accounts inside of your database there we go, I have two test accounts right here and if I try another account and click create this will automatically get added. Why does it automatically get added? Remember let's take a look at our use create account hook since I have features accounts API use create account. What we do on success is we invalidated the queries accounts which is this one use get accounts which means it will refetch every time you successfully create an account.
Great so we resolved that Let's head back inside of accounts page.psx right now. What I want to do is I want to create a loading state. So let's go ahead and do that now. So I'm going to go ahead inside of my terminal and I'm gonna add a component bunx-chatcnui-latest and I'm gonna add skeleton like that. Great.
Bun run dev again. Then we're gonna write here if accounts query is loading. In that case let's go ahead and let's just return this div exactly as it is. So I want it to be the same. I also want to copy the card element.
I wanted that to be the same. I also want to copy the card header element. So that is also the same for me here. But we don't have to add any classes here. Like that.
So let's just add card header. Card header. Now inside of here I'm gonna go ahead and add a skeleton component from components UI skeleton like that. Let's self-close this tag and let's give it a class name, H8 and width of 48, like that. And then inside of card content here we're gonna add a div with a class name of height 500 pixels full width flex, item center and justify center and inside we're gonna add a loader to a Lucid React icon So Loader2 from Lucid React and we're gonna give it a class name Size 6, text Slate 300 and animate spin Like that!
So if I go ahead and refresh my page now, I think for a slight second there we go we have a nice loading animation here. Perfect. So what I want to build now is the ability to delete or more specifically to bulk delete my accounts right here. So usually for delete actions we would use the delete API method but since this is going to be a bulk delete we need to pass the body and in the body I want to pass an array of IDs to remove. Because of that I want to use the POST request.
I actually searched for some solutions about what other people do here and most common response was to simply use the POST request. So I think it is an actual practice that's done in real world that when we have a bulk delete it might be a better idea to use a POST request. So let's go inside of accounts right here let's close GET and we have POST here so let me just see I can indent that like this I will remove the semicolon and I will chain another post inside like this. So inside of this post it's gonna go to slash bulk dash delete. It's gonna use clerk middleware and z-validator is gonna be a bit different.
So usually we reuse some schema but this time we're going to write our own z.object which means we need to import Zod specifically. So Z from Zod. Let's go ahead and see. Oh, my apologies, I didn't add it in a string. So it's going to be Z.object.
So we are validating the JSON body field. All we need is IDs which is going to be Z.array of strings like that. Perfect. And then finally we are going to create a controller here so let's get the context and inside of here first of all let's get out using get out c then let's get values using c request valid json if there is no out user id with a question mark in that case we're going to return c.json error unauthorized. With a status of 401, remember that is very important.
And then inside of here, let's go ahead and let's write const data to be await database.deleteAccounts.where and now we're going to chain multiple filters so let's add and which you can import from a drizzle or ram and let's also add in array so those are the two we need so and we need both equals accounts user id to match out user id so we are ensuring that the user can only delete their own accounts and the second in array of what we just passed so accounts.id need to match values.id which we pass right here in the json and validate that here like that and let's go ahead and let's chain.returning and let's be specific and only write accounts.id so that's the only thing we care about the return and let's return c.json here data there we go so now that we have that what we can do is we can create the features accounts API use bulk delete so let's do that again features accounts API and let's create a new hook useBulkDelete.ts. And what we can do to speed things up a bit for us is we can copy useCreateAccount. So go ahead and copy the entire thing and paste it in use bulk delete because both of these are mutations.
So first of all, I'm gonna go ahead and modify my response type. So I'm gonna zoom out just slightly so you can see me write this in one line. So in order to access this we can't write .bulk-delete right we need to use the array method of accessing something so there we go bulk delete And then we have to chain another post. So we can't continue here with dot post. It's not going to work.
We need to chain it like this. Post. Like that. And we need to copy this and do the same thing here like that so make sure you have a request type and make sure you have sorry make sure you have a response type like this and make sure you have a request type. So request type only has IDs, right?
Our request response type can be either an error or returning the deleted IDs in the array. Great! Now let's go ahead and modify this method right here and let's actually rename the query here. So use bulk delete accounts. Now let's change this mutation function which now accepts IDs as a string to not directly go to the post but instead it needs to go again to bulk delete and since you are calling this as a function you can now chain.post if you want to or you can use the same method as above post like this and then inside you would pass in JSON again.
There we go. So let me zoom out so you can see. API accounts bulk delete post and you're passing in the JSON exactly as it expects. So now in the on success the message would be accounts deleted and the invalidate queries here would well still do accounts but I'm gonna add a to-do. Also invalidate summary.
So we don't have summary yet but I don't want to forget this and later I'm going to search through my to-dos to ensure that everything is working fine. And on error is going to say failed to delete accounts. There we go. So we are ready to try this out now. So let's go ahead and let's go back inside of our page.tsx here in dashboard accounts page and now what I'm gonna do is I'm going to add the delete accounts from use bulk delete accounts now I'm gonna go ahead and I'm going to move this import alongside these two right here So now we have the delete accounts query and what I want to do is I want to add a little const is disabled here to be the following if accounts query is loading or if delete accounts dot is pending.
So one of the two, right? Now let's go ahead and let's find our data table here and let's do the following. Let's change the disabled from hard-coded false to isDisabled and let's go ahead and modify the on delete now here to do the following it's going to accept row and then we can get IDs using row map are are dot original dot ID and then we can call delete accounts dot mutate and we can pass in the IDs like this. But this is not going to work now because we are missing one thing here which is to actually call the on delete inside of data table. So let's go inside of components data table.
So we are not calling the on delete prop anywhere in our code yet. So how about we change that? Go ahead and find the button where you have the trash icon and the delete text and add an on click here. On click is very simply gonna do on delete, table, get filtered, select row model, execute it and pass in the rows and then do table reset row selection let's go ahead and try it out now so I'm gonna go ahead and refresh the entire thing I'm gonna select everything and I'm going to delete it and there we go accounts are deleted when I refresh they are nowhere to be seen if I go to slash api slash accounts they are completely empty great so if I add first account it's created I'm gonna add a second account And I'm gonna add a third account. Like that.
Let's go ahead and select just this one and there we go. Accounts deleted. Perfect. So this is great but since this bulk delete action is quite powerful, it can delete a lot of stuff at once, I want to add a confirmation model. So I want to do that in a way that I can easily reuse it as a hook everywhere.
And I found out a method on how to do that. So let's go ahead and develop that. So first of all, I wanna give credit where credit is due. I found that on this Medium blog post right here. So thank you, Isaac.
I also think that there is another name here worth mentioning. Roy. So thank you both for this great blog post. So I'm going to go ahead and implement what they talked about inside of that blog post here. So what I'm gonna do is I'm gonna create a reusable common hooks component and inside of here I'm gonna create use-confirm.tsx.
So tsx is important not just ts, tsx. Let's go ahead and let's import useState from React. Let's import our button from components UI button. Let's import everything we need from components UI dialog which we don't have. So let's quickly head inside of the terminal here and do bannex chat-cnui and let's add the dialog component.
There we go. So I'm gonna go ahead and import everything we need. We need the dialog, the dialog content, the dialog description, the footer, the header and the title. So those are the items that we need. And let's export Confuse Confirm right here.
So these are the props. We're gonna have title which is a string. We're gonna have a message which is a string as well. And what this is going to return is going to be the following it's going to be a method which returns JSX dot element so that's the first thing and then another one which returns promise and unknown that's going to be our function right here So right now we get an error because we don't return that but we are gonna do that later. So let's go ahead and do the following.
Let's go ahead and create a very simple useState here which is by default going to be null, but the type of this state is going to be an object which has resolve, which is a method which has a value of boolean and it returns a void. Or, outside of the object, it can be null. So that's what it's going to be, right? And inside of const we write promise and set promise as the second argument. Now let's go ahead and let's write const on confirm.
My apologies, just confirm. Let's make this an arrow function which calls new promise inside of the promise it calls resolve and reject as props for the inner method which very simply is going to call set promise open an object and write resolve inside like this then we're gonna have handle close which is very simply going to set promise to null kind of like resetting the promise and inside of here we're going to have handle confirm So const handle confirm which is going to call promise question mark resolve and pass in the boolean true and call handle close to reset this hook. And let's have const handle cancel it's going to work similarly to close but it's going to explicitly resolve the promise with a false boolean like this. Great! And finally let's create const confirmation.
It's important that you call this confirmation dialog method with a capital C because it's going to be used as an element. So immediately return a dialog. It's going to be open if promise is not null. So every time we call handle close the dialog will close. Inside of the dialog add dialog content, a dialog header, and a dialog title.
And inside of the title you can simply render the title and below the title add the dialogue description and render the message. And now still inside of dialogue content but outside of dialogue header add the dialogue footer component. Give it a class name of PaddingTopOfTwo. Inside of DialogFooter add a Button component, which we already have imported, and write Cancel. Let's go ahead and give this an onClick on HandleCancel and a variant of outline.
Go ahead and copy and paste this button inside of the footer still and this one will just have handle confirm without any specific variant. And it's going to say confirm. And now to fix our TypeScript error we have to return an array. First confirm dialog, second confirm. There we go.
We have a hook which has no TypeScript errors and let me just zoom out so you can hopefully see this in one line again. Like that. And now let's go ahead and let's use this here. So I'm gonna go back inside of my data table and you're gonna see how cool this component is. Well, more specifically this hook is.
So I'm gonna go ahead and I'm going to call useConfirm from hooks useConfirm like this. And now let's go ahead inside of the data table method and at the top here let's add use confirm and for the title we're gonna say are you sure and for the description we're gonna write you are about to perform a bulk delete. And now from here let's extract the confirmed dialog. You can name this whatever you want right? So this is a named constant and the second argument is our confirm method.
So this is what we're gonna do first. Let's go ahead and first let's render the confirm dialog anywhere in our code. It's important that it's somewhere right in this component and then what we're gonna do is we're gonna find our delete method here so we have on click on delete here Let's change this to be an asynchronous method and inside of here we're gonna get const ok to be await confirm. Are you kind of seeing what's going to happen now? So inside of here if we confirm we're going to pass in true and then this constant right here, okay, is going to become true.
But if we pass in false, it's going to become false. So this is a very cool use of promises, async and await. And now what we can do is very simply if okay only then remove and call on delete. Let's try it out. Let's see if we made any mistakes here.
So make sure you have some accounts here. Make sure you have the app running. Refresh. Let me select everything and click delete. And there we go.
If I click cancel, nothing happens. But if I go ahead and confirm, they are deleted. Amazing. Great, great job. What we are going to do next is we are going to add two more endpoints and that is so that we can open this test in a same drawer that we are going to reuse and deform.
So we need an endpoint to fetch by ID and we also need an endpoint to patch or update the name of the account. Great, great job! Thank you.