Great, so now that we have our settings page finished, what I want to do is I want to set up our database. And the reason I want to do that already is because the next thing I want to teach you is how to use the new server actions. We're going to start from the most primitive way of using server actions and then we're slowly going to abstract them into our own custom hook and our own custom util which is going to make those server actions completely type safe and also validated using Zod. So the first thing I want you to do is head inside of your terminal. So let's go ahead and open that and you can feel free to shut down the app if you are running it in the same terminal.
And let's go ahead and npm install d prisma like that and let's wait a second for this to finish and after it's finished let's go ahead and run npx prisma init and that's going to generate a couple of files inside of our project. So let's take a look at what those files are. As you can see now we have a Prisma folder and we have a schema.prisma file inside. And inside of our .environment file you can see that after our environment keys for clerk we now have this big comment that this was inserted by Prisma in it and we have a mock database URL so this is not going to work this is just a dummy placeholder but we are going to replace that with our actual database URL. So let's go ahead and do that now.
So for my solution I'm going to use PlanetScale. PlanetScale offers a completely free unlimited database but it does require a credit card to prevent abuse of that free tier by creating more accounts. So if you don't have a credit card for any reason or just don't want to use it you don't have to use PlanetScale so the only important thing is that you use some kind of SQL. If you want to you can set up your own local Docker MySQL instance or you can Google PlanetScale free alternative. The reason I'm using PlanetScale is because it's by far the fastest way to create a production-ready MySQL database with load balancers and of course amazing Prisma support for migrations.
So I'm going to show you how to do it with PlanetScale. Again, if you don't have a credit card or don't want to use it, you don't have to. You can just Google how to set up MySQL locally because the only thing we need from PlanetScale is this database URL. That's the only thing we need. Okay so head to planetscale.com or whatever your solution is and just go ahead and sign in.
After you sign in go ahead and find the button which says new database. Make sure you don't have any other databases active. So this is my paid database. That's why I have it. But you always have one free database.
So go ahead and create a new database and give it a name. In my case, it's going to be Trello Tutorial. And as you can see, you have the option Hobby, which is free forever. But as I said it does require a credit card. Again if you don't want to do that you can just find a local solution for MySQL.
Those are always free. So make sure you select the free option and just go ahead and click create database and confirm one more time here that total monthly cost is free and click create a database and while that is creating let's go ahead and let's select our development instance so as you can see we have a lot of options here, but the one we are using is Prisma. So go ahead and select Prisma like this. And here we have to create a password for our database, which is in the same way going to create our database URL. So go ahead and click create password the name does not matter.
If you want to you can copy the username and password and store it somewhere safe. If you accidentally lose it you can always create a new password but you cannot see it after it's been generated right so make sure you copy it again if you forget it it's it's not a big problem you can always generate a new one for the same database but you cannot see it immediately after you copy it. Great, so now it says that we have to configure our Prisma application. We already did this and we run this command line and go ahead and make sure this optimized is selected for the lowest latency between our application and the actual database. And there we go.
Here we have the database URL. So this is what we need. So let's go ahead and copy that. And let's replace this database URL with that solution. And there we go.
I have my database URL right here. Again, if you're doing this in a different solution, all that matters is that you find the equivalent MySQL database URL. And the other thing that matters that you do is update the Prisma clients. So let's go ahead. We can skip over this.
We don't need that. What we actually need is this, sorry, I scrolled too far. We have to modify our schema Prisma. So let's take a look at our schema Prisma to see the differences so I'm gonna go inside of Prisma folder schema.prisma and as you can see we have to change the provider to be MySQL. We have to change the URL to be a database URL.
Actually, that is the same. And we have to change the relation mode to Prisma. So what I'm going to do is just copy this entire thing and replace it with this. So again, if you're not using PlanetScale, you can do this manually just by looking at what I did. Provider is MySQL, URL is the environment variable database URL, relation mode is Prisma and provider is Prisma Client JS.
Perfect, and that's it. You don't have to look at PlanetScale again. You can close it completely. So again, just ensure that in your dot environment where you have all the clerk environment keys you now have your database url and in screenmap prisma you modified it to look like this. And now what I want to do is I want to create our first model here So go ahead and write model board.
Let's go ahead and give it an ID, which is a string, type of ID and the default value of UUID. And now let's go ahead and simply give it a title, which is a string. And once you add a new model inside of your schema Prisma you have to head inside of your terminal here and you have to run the following command npx prisma generate so this is the first command which will locally create types and well functions for this new model which we added. What does it mean locally? It means that it's added inside of your node modules so with the next package we're gonna install which is this Prisma client and then when we use our Prisma client like this, const Prisma, new Prisma client and then when we write Prisma.board you're gonna see no errors because it's added locally so that's the first thing you have to do whenever you add a new model or modify a model inside of your database.
And the second thing you have to run is npx prisma db push. What this is going to do is push it in my case to planet scale, so the planet scale is in sync with what we have. And whenever you do these changes, npx prisma db push and npx prisma generate make sure that you also restart your application so if you haven't already shut down your application and then just npm run dev again But we're not gonna do that just yet because the first thing I want to do is do npm install at prisma slash client. So this is going to allow us to use Prisma in our application and now we can npm run dev. Great So now that we have this saved, let's go ahead and create a little library which we are going to use to access our database.
So inside of the lib folder create a new file db.ts. Go ahead and import the Prisma client from at Prisma slash client and write export const db to be global this dot Prisma or new Prisma client. And you can ignore these errors for now. We're gonna resolve them in a moment. And now let's write an if clause.
If process.environment.nodeEnvironment is not production, so if we are either in development or local or something like that, in that case globalThis.prisma will be our database constant. And now let's go ahead and fix these errors. You can see our global this module doesn't have Prisma registered. We can do that quite easily by adding a declare global var Prisma to be a type of Prisma client or undefined. Like that.
And now let's briefly explain why we are doing this. So in production, this is what's going to happen. You can imagine that we didn't write this. So this is what would happen. We wouldn't even technically need this.
This is the most primitive way to export our database which uses the new Prisma client. So why do we do all of this for our development mode or anything that's not production? Well that's because Next.js uses hot reload and during that hot reload new Prisma client will be initialized multiple times and that's going to create a warning inside of your project. So what we do is we do the following. So when we define the database variable, we check if we have it stored in global this.prisma.
If we don't, meaning this is the first time we just started the app, in that case, we just initialize new Prisma client. And then since we are in development because of the check in this if clause we assign that database constant to global this dot Prisma and then when hot reload activates because we change some other file all it does it looks like at this global this dot Prisma and since we already have it here it knows that it doesn't have to initialize it again and if you're wondering why doesn't hot reload affect global this dot Prisma well that is because global is excluded from hot reload. So that's a brief explanation of why we are doing that. So what I'm going to do now is create the most primitive server action. And I'm going to do that inside of app folder, platform, dashboard, organization, organization ID, page.tsx.
So right here where we have the text organization page which is currently rendered right here and we can access that component by clicking on the boards in an individual workspace. So just make sure you refresh your applications so everything is up to date because we just did a lot of turning on and turning off. Make sure you see the organization page text and we're going to do the following. We're going to replace this text right here with a form. So I'm gonna write the native HTML form element.
I'm gonna add a native HTML input here. I'm gonna give it an ID of title. I'm going to give it a name of title. And I'm also gonna make sure it's required like this and let's go ahead and see if we can see that right here okay it's right here but let me just go ahead and give it a little placeholder so we can see what we are typing so enter a board title and let me just give it a class name border input or maybe just border black it doesn't really matter and border there we go and a little padding one great So just something that we can see clearer. Perfect.
So we have this little input here. And one thing that I want to just ensure here is that organization ID page is a server component. So you can either simply know that by having you know experience in the app router As I said a couple of times, every page that you create is automatically a server component except if it is rendered inside of a client component which you already learned we defined by adding useClient at the top. So now this becomes a client component and let's see the difference now. So I'm gonna add a console log.
I am rendered in the browser. Sorry, I am logged in the browser. That would be a better idea. So make sure you have used client and add this console log right here. Refresh your page and open up your console.
Just a second, let me assign my console right here. There we go. And you can see my logs here. I'm logged in the browser. You can see how it's visible right here.
But what happens if I remove use client? Let's try that. So I'm going to go ahead and expand my screen and refresh the entire thing and now as you can see I no longer have those logs. So where is that log? Well that log is visible inside of our terminal right here as you can see there it is I'm logged in the browser well the message stayed the same but you see what's the difference this is obviously rendered on the server Meaning that the logs from this component are only visible in our terminal because our terminal is running the server.
Great! So we ensured that this is a server component. Make sure you don't have useClient at the top. So if you watched any of my previous tutorials, you know that with server components, we can do cool stuff, but like directly fetching the database, right? But one solution that we didn't have until now, at least not in the stable version, is how to mutate data from server components.
That was one mystery that we had. So what I want to do now is I want to add an asynchronous function create which accepts form data which is a type of form data which you don't have to import from anywhere we just have it and inside we're gonna add use server like this and then let's console.log for now I am triggered and let's add this async function create inside the form action like this and let's go ahead and open our terminal so we can see the logs here. So I'm gonna refresh my page now and I'm gonna write test and I'm gonna press enter and once I press enter you can see that I have a message here I am triggered meaning that this function which we created is successfully triggered inside of a server component. If this doesn't look if this looks like completely ordinary to you You have to understand that in server components usually you were not able to pass any functions or any hooks anything like that. So that's why if you come from my previous tutorials this is a new thing for you.
But if you came from purely a single page application world this probably looks like a normal function to you. But there is a difference because this is entirely run on the server. This is a server component. And because of this user server declarative, we can now access our database and use this form data to, well, create a record. So let me show you exactly how we can do that.
Right here. I'm going to write const title to be form data dot get title. And then what I'm going to do is I'm going to call our database and I'm going to import it from add slash lib db. So it's that function which we created recently. And then I'm gonna write db.board.create.
And I'm gonna pass in the data and I'm gonna pass in the title. And as you can see, now we have a little type error here. So let's just mark this form data as string. There we go. And before we run this let's go ahead inside of our terminal here.
I'm going to open a new one and I'm going to write npx prisma studio. So let me just expand my screen so you can see it in one line. Npx prisma studio and press enter and that's going to be running the prisma studio which is basically a ui for our database as you can see it noticed that we have defined the model board in our prisma schema but we have zero records of that board So let's see if this function now it's working. So I'm gonna try and add a test here and press ENTER. Let's refresh here and it looks like it's not here so let's go ahead and check out what's going on.
Alright so my apologies I forgot that we have to put a wait here. So make sure you put a wait inside of your before your DB board create like that. And let's try this again. So I'm going to refresh this, I'm going to refresh my Prisma Studio, no records here, I'm going to write test, I'm going to press Enter. And now when I refresh here, there we go.
You can see I have a new record inside of my database. So as you can see we just used a React component to do a create method or what would usually be a post API request. That's really cool. Of course, there are discussions going around whether this is actually good or not. Some people with very valid reasons may still prefer API routes and that's completely fine.
Now depending on the type of application you have, if it's an enterprise application, this is definitely something to discuss first. If you're an indie hacker, this is just a very fast solution for you. So you know, it comes down to your preference. I personally think it's great that we have this many options. In this tutorial I'm not really going to discuss whether this is always the best solution.
Instead, I'm going to focus on mastering this server actions as much as we can so you at the end of the tutorial can decide yourself whether this is something you like or not. Great! So the next thing that I want to do is I want to show you how to separate this inside of a different file. Because sure this is great but you know we kind of have the problem of separating concerns. I would kind of like that this organization id page is only responsible for id and passing the actions but not exactly for defining actions because as you know in the API so this is representing our API We have to check for authentication, we have to check for errors, you know, a bunch of different stuff here.
So that's what I wanna do next, is separate this into its own file. So let's go ahead and do the following. First thing I want to do is go ahead inside of my terminal here and I'm gonna shut it down for a second and I'm just gonna run npm install Zod like this and I'm just gonna run my app again. Make sure you have Zod installed and refresh your localhost if you shut down the app. Then I'm gonna go ahead and copy this function from here and remove it at the same time and I'm gonna go and I'm gonna create a new folder called actions and inside I'm gonna create a new file createboard.ts.
Actually I'm gonna use create dashboard.ts like this and let's go ahead and paste that here and let's remove this use server from here and instead let's just go ahead and put it at the top like that and then let's just import the database from s-libDB like that and let's also do an export for this asynchronous function right here. Great! And now let's go ahead and remove this. And let's now import create from actions create board. And let's confirm that the same thing is working.
So I have one record in my database here. I'm going to try and create a new one. Make sure you just press enter here. Let's refresh and there we go. It's still working.
I have two items in my database now. So one thing I want to do before we move on is just add a little button here which says submit and give it a type of submit just so we don't have to press enter all the time. And in fact, we can use the button component from components UI button like that. There we go, this looks a bit better. So let's go ahead and refresh, type one to three, click on submit.
Let's refresh here. And there we go, we have our third record here. And you probably already noticed that one thing that's missing are the loading states and also the errors right the only kind of validation that we have right now is this native HTML required field because we put the required prop inside of the input so now let's go ahead inside of create board right here and let's make use of that Zod library So go ahead and import Z from Zod and let's create our schema. So const create board is going to be Z.object which accepts the title which is a type of Z.string like this. Great.
Now let's go ahead and use this create board to pass our form data to confirm that everything is working as expected. Everything has the exact types that we want. So I'm gonna go ahead and write const. And from here, I'm gonna extract the title and I'm gonna write create board dot parse. And then I'm gonna write the title to be form data dot get title, like that.
And as you can see now, we no longer have to write that as a string because right here you can see that Zod is going to ensure that title is a string for us So let's go ahead and save that and let's see if it's still working. I'm gonna write in Zod and click submit here. Let's refresh and there we go it is still working because our input is a string and that validates this parse right here. Before we move on into extracting the errors from this right so we can display it back to our users what I want to do is I want to show you how to actually see those results and how to fetch them. So since this is a server component we can do the following we can write const boards to be await DB from s-libDB.board.findMany and that's actually the only thing we have to do and since we're using a weight we have to turn this into an asynchronous function and then let's go ahead and give this a class name of FlexFlexCall and space y4 and below this form let's go ahead and open a div with the class name space y2 and let's iterate over the board so boards.map get the individual word like that open up a div with a key to be board.id and let's write board name to be board.title so actually it's gonna be board.title here.
There we go you can see that now I have my four records here as well as in the database But if I try to create a new one the new one was just created. So let's check in my database. There we go. You can see this is the newest one here. But it's not visible here until I refresh.
So let me show you how you can revalidate your path inside of well inside of your Create action. So what I'm gonna do for now is I'm just gonna copy my URL. So make sure you do the same thing. Copy the entire URL and go back inside of your create function here and we're gonna go ahead and we're going to add a revalidate path function from next slash cache like this and I'm gonna pass in my URL but I'm gonna go ahead and remove the localhost part of it. So just slash organization and then my organization ID.
So the reason I copied the URL is because we are working with dynamic URLs, right? So it's just simply easier to copy it now, but don't worry, I'm gonna teach you how to, of course, pass the actual ID so you can do this programmatically. So make sure you have that. Let's refresh and let's write real-time. Click submit.
And there we go. You can see how now it was created in real-time here and updated. So now I want to show you how to... We learned how to create, but how about deleting or updating a specific board? Well, obviously we can kind of guess how we would do that, right?
But what we don't know is how do we pass an ID inside of this server action? Well, in order to do that, let's go back inside of this page.tsx and let's go ahead and copy this div, which represents our board and inside of this organization ID where we have the page.dsx let's go ahead and just create a new file called board.dsx so we're gonna delete this later I just want to create a quick component here So const delete board and let's go ahead and return, well, the same thing we just rendered, right? But this time we don't need this key. And let's go ahead and create a quick interface for this. So interface delete board props.
Actually, let's call it, I don't know why I called it delete board. It's gonna be board. So board props, it's gonna have a title, which is a string and an ID, which is a string. So let's assign those props here, board props, and then let's extract the title and the ID and then we can render the title here. Go back inside of page.dsx and instead of this div we can now render that board component and let's just make sure that we do an export here so export const board and then we're gonna be able to import that from slash board like I did right here and we just have to pass in the key to be board.id here now and then let's go ahead and give it a title which is board.title and id which is board.id so make sure you have these three props here and as you can see nothing much has changed but what I want to do now is I want to go ahead and turn this into a form as well and let's go ahead and do the following.
Let's give it a class name of FlexItemCenter and get x of 2 and let's wrap this into a paragraph like this and then let's add a little button component from UI button which is going to render delete and give it a variant of destructive. Whoops. And let's give it a size of small, like this. Great, so now we have a little delete button here. Right now it's not doing anything as you can see it's just refreshing the page because we are we've wrapped it inside of a form.
So let's just give it a type of submit here and now what I want to do is I want to create a new server action which is going to be used for deleting our boards. So let's go ahead inside of actions and create a new file delete-board.ts like that. Let's go ahead and export asynchronous function, delete board, which passes in the ID, which is a type of string. And what I want to do in here, actually it's not an arrow function, sorry. What I want to do in here is simply await db.board.delete where and passing the ID like this.
And then I'm just going to go back inside of my create board and I'm going to copy this revalidate path just make sure that you are in the same organization as you were when you copied this and make sure you import revalidate path from next cache. Great So now we have the delete board right here and now we have to find a way to pass in this ID. So let's go ahead inside of board.dsx here and let's go ahead and let's write const delete board with id to be delete board which we just created so import it from add actions delete board and then do dot bind null and then passing the ID like that and give this form an action of delete board with ID like that. And as you can see, we have a little error here. That's because inside of my delete dashboard, I forgot to write use server at the top.
And when I save, as you can see, we no longer have any errors. So let's try this out. I'm gonna click delete here and there we go. You can see because it revalidates the path, it's deleted in real time. So that's how you can pass in a specific ID to the server action.
So what is the main purpose of server actions? Well, obviously, allowing us to do mutations from server components. But one thing that is heavily mentioned inside of Next.js documentation is progressive enhancement, which, as I understand it, allows us to do these mutations without JavaScript being active, making it thus much faster than usual API calls. Great. And what I want to show you now is how to create loading states as well as displaying errors in our fields so the only way that I know this can be done is by mix and match of server components and client components so I'm gonna close everything just so I clear my view.
I'm going to go ahead and find this page.tsx where we have the form. And what I'm going to do is I'm going to go ahead and change this form right here to be a client component. So I'm gonna go ahead and just as I created this board.tsx I'm gonna create a new form.tsx. I'm gonna mark it as use client and I'm going to export const form and I'm simply going to render those actions here and let's import the button from here, from UI button and now we have a little problem with this create function here so for now let's just go ahead and import it like this. And then go back to page and import that form from .slash form the same way you did with .slash board.
And we don't need this too anymore. Great, so now we have our form component and the first thing I want to add is a hook called useFormState. So const, let's just add an empty array for now to be useFormState from react-dom and in the first argument we're going to pass in the create method. And then I'm going to create a constant above called initialState to have a message of null and errors, which is an empty object. And the second argument is going to be initial state so this kind of works like a reducer and then I'm gonna go ahead and extract the state and dispatch from this use form state here and now let's go ahead back inside of this create function and modify it slightly because when used in use form state which is going to give us like some pending state and some errors we have to also modify this but just before we do that we are no longer going to be using the create itself here explicitly instead we're gonna be using the dispatch so make sure you change your form action to use the dispatch like that now let's go ahead and go back inside of our actions create board and inside of my Z object I'm gonna add a requirement that a minimum length is 3 like that and we can also add a let's see is it the message or invalid I think it's a message I'm just gonna say minimum length of three letters is required so a type of error right So now our Zod has an error here.
And now let's go ahead and let's create a type here, sorry, export type state, which is going to have an optional errors object, which is going to have an optional errors array of errors for the title. And we're going to have a message, which is a string or null. So as you can see, it's going to match our initial state here errors which is an object and message which is a null and then I'm going to tweak this function create that before it accepts the form data is going to accept the previous state which is going to be a type of state which we just created above. Great! So now we have that.
So now let's go ahead and let's modify this create board parse to use safe parse like that. So parse will throw an error and break the application whereas safe parse will not do that. So let's go ahead and change this from the structured title to be validated fields and then we can use that to check if we have any form errors like a too short of a title. So let's write if not validated fields dot success So make sure you put an exclamation point before that. In that case, we are going to return an errors object, which is going to be validated fields dot error dot flatten dot field errors and a message is going to be missing fields like that And then instead of using title like this what we're gonna do is extract the title from validated fields.data just like that and in order to handle this type of message error let's go ahead and wrap this into a try and catch method so I'm going to go ahead and add a catch here which is going to have our error and then we're going to return a message database error like that and lastly alongside revalidate path let's also add redirect from next slash navigation so make sure you import redirect and we're going to redirect to that very same route like that perfect and you can see that now when I added this redirect if you paid attention all the errors inside of my form.csx have disappeared So what exactly did we achieve with this?
How can we actually look at our errors now? Well we can do that by accessing this state right here which is going to have this kind of object. So you can take a look that if our Zod validation fails, that means that we're going to have an array of errors for a specific field which matches whatever this schema is trying to validate. So below this input right here, I'm gonna go ahead and let's let's just do it like this I'm gonna wrap this div I'm gonna wrap this input inside of a div I'm gonna write a class name of flex flex dash call and space y to like that and then in here I'm gonna go ahead and write if we have state errors and let's just not misspell state so if we have state errors title in that case let's go ahead and let's add a div here and let's add an else to be null and inside of this div let's go ahead and iterate over state.errors.title.map getIndividualError which is a type of string and let's go ahead and add a paragraph here which is going to render that error give it a key of error and a class name TextRows500 like that.
So now let's try this out. I'm gonna refresh here and I'm gonna enter a title which is only one letter. And let's see, there we go. We have an error now which says that minimum length of three letters is required. So again, if this is not impressive to you, you're probably coming from a background of single page applications, right?
But this is really cool because we're technically doing this client level stuff purely inside of a server component, which has progressive enhancement and not a single line of JavaScript is needed. Sorry, let me rephrase this. You don't have to have JavaScript enabled in your browser for you to do these actions. So that's one of the main things of server actions which they allow us. Great so now you know how to create how to create errors right but one thing that you still don't know how to do is how to handle loading states right when I go ahead and create something let's say it's successful I wanted this little submit button to be disabled or maybe even the input itself I want it to be disabled.
So how do we do that? So what I want to do now is I want to separate my input component into a separate component as well as my button component so I'm gonna do that right here. I'm going to copy this input component here and I'm just gonna create it here like it doesn't really matter because we're gonna remove these files later So go ahead and create input.tsx or let's call it form input.tsx like that. And let's go ahead and do export const form input and let's just return this input like that. And what I want to do next is I also want to pass in these errors, right?
So I want our component to be able to render both of those. So for that, I'm going to wrap the entire thing inside of a div. Like this. Great. So now we have to create a little prop which is going to accept those errors.
So I'm going to create an interface form input props, which is going to accept an errors, which is going to be an optional. Well, let's just do it a record string any, like that. And then let's go ahead and assign that. So form input props, let's extract the errors. And then we can just use the errors directly and the errors directly like this or perhaps we can do it in a better way we can just pass in the title which is optional and the string of arrays because we know that's what it's going to look like.
Of course, you can see that this form input component would not exactly be reusable because we are fixating on the title prop but this is just for example, right? So make sure you do this and then we can remove this entire input and we can use the form input from ./.forminput so make sure you add that import here great and let's pass in the errors which are going to be state.errors like that. And I'm just going to pass in the question mark here just in case state is undefined. All right. And right now I think everything should still be working exactly the same.
So I'm going to write a short title and there we go. I still have my errors. So how do I disable this while it is submitting? Well for that we can use a hook called useFormStatus. So let's go ahead and first let's mark this as useClient that's important.
So useClient and then write a constant here use form status from react dom so you can see no external libraries here we are purely using what react offers us and from here let's extract the pending state And now let's go ahead and let's add a disabled let's go at the end here and add the disabled prop to be when it's pending and let's actually replace this input to use the input from ShadCNUI we don't have it so head into the terminal here let me just shut down my Prisma Studio and run npx shad-cn-ui-latest-add-input like that so wait a second for that to install all right and let's replace this native HTML input with the input from the components UI input. Great, and we can remove this class name now. So I had this class name for BorderBlack, but we don't need it if we're using the input from ChatCM. Great, And now let's take a look if anything changes. So I'm gonna write test here.
I will click submit and you can see how it was disabled for a second. You can see even when I have an error but especially now you saw for a second how I have a little cursor, my cursor changed that I cannot edit this so we successfully used this use form status when does this use form status work where does this pending come from well it works because our form input component is inside of a form component so that's why it is working And we can do the exact same thing for this button. So let's copy this button component and let's create a new one called form-button.tsx. So I'm just creating a random components here and let's do export const form-button. And let's just return this items here, let's import the button from chat-cn and let's go ahead and let's add const use form status from react-dom and let's extract the pending and let's go ahead and give it a disabled prop of pending like this And now go back inside of form right here and replace this button with form button component.
Like that. So now our submit should also be disabled when it's creating. There we go. You can see how my button is disabled. Perfect.
And using the exact same method we can do it for the delete button right here. So let's just quickly do that so we wrap up this thing. So we have this board component where we have the delete button. I'm going to create a new one called formDelete.tsx So let me just show you, there we go, formDelete.tsx export const formDelete And let's just import this button from components UI button And let's go ahead and let's add const pending from use form status from react-dom and give it a disabled prop when it's pending and then let's go back inside of our board and replace this with form delete from this component which we just created right here and let's go ahead and make sure that form delete is a client component so use client so we get rid of this error and let's see if that is enough for this to work when I click delete. There we go.
You can see how it's disabled until it is finished. Perfect. So you just learned a bunch of new things about server components. You see everything that is available now. And what we're going to do in the next part is we're gonna start creating some abstractions around this.
Basically, I want to create a reusable way to use these server actions because sure this is great but it seems like a lot of work, right? And that's not at fault for the server actions, you know. We have to create a structure for it. Keep in mind that we are all probably a little spoiled by these amazing libraries like React Query, which offers an easy use mutation and immediately gives us the loading status and the success message, the error message, all of those things. So we're going to attempt to recreate a use mutation kind of to create our reusable server components.
And basically there is not going to be a single API endpoint which does post, patch or delete in this tutorial. We are entirely going to use server actions, even for generating the Stripe checkout page. The only place where we're going to use API calls is to fetch some information inside of those drag-and-drop components which simply have to be client components no matter how much I tried to make them server components I couldn't do it because the package itself requires work with client components but that's not a big deal you know the the existence of server components doesn't make it so that using client components is something bad. If you want to you can use client components absolutely everywhere You just always have an option to use server components which definitely have its perks and right now they might seem complicated it might seem a bit weird but keep in mind that they literally just came out So we still have months and years until people create amazing packages around them and we're gonna have something like React Query but for server components very soon I'm sure of it. Great, great job so far and let's go ahead and start creating those abstractions now.