Now that we have our abstraction around server actions, let's go ahead and let's create some custom components similar to these ones which we just worked with, which are the custom input component which we named form input, custom form button and similar to that. So I'm going to go ahead and close everything here and I'm going to go inside of my components folder and in here I'm going to go ahead and create a new folder called form like that. So inside of here I'm going to keep everything regarding my reusable forms. So first thing I want to create is the actual form input which has the same name as the one we created in the organization ID folder. We're going to delete it later.
But for now, I just want to create a reusable component that is going to behave similarly to that one. So first things first, we're going to mark it as used client because I already know that we're going to use that use form status to extract the pending state from it so this can easily be used inside of any form and it will automatically be pending once the form is submitting as well as the fact that we're going to have a lot of handlers like on blur on change and similar to those so let's create the interface form input props and first things first we're going to need an ID, we're going to have an optional label, we're going to have an optional string, sorry an optional type, an optional placeholder, so all of these are type of string, then we're going to have a required field, which is an optional boolean, a disabled field if we want to manually disable it, and we're going to have some errors, which are going to be a record, string and string array or undefined. My apologies, undefined goes here so like this. Alright and then we're going to have a optional class name if we want to modify the style of this input.
We're going to have default value, which is going to be an optional string. And let's just not misspell this. So default value. And we're going to have on blur, which is going to be an optional void. So that's all we're going to need for now because let's go ahead now and let's do export const form input and first things first that I want to do is I want to enable ref forwarding inside of this component so we're going to write forward ref and we're going to import that from react so let's go ahead and let's give it the types of HTML input element and form input props like that great go ahead and open parentheses and go ahead and open parentheses again and this represents the props so inside let's extract the ID, label, the type, not the prop types my apologies just the type, placeholder, required, disabled, errors, class name, default value and we're also going to have on blur and let's go ahead and give the default value a type of empty string like that and then in here add a comma after this destructured props and extract the ref and then before this parentheses and go ahead and open up this function and now let's go ahead and before we do anything so we get rid of this error whenever you use forward ref you need to add a display name and we can do that quite easily by just adding form input dot display name to be form input and there we go great And now let's go ahead and let's actually extract pending from use form status which we can import right here from React DOM and then let's go ahead and let's return a div with a class name of space y2 then inside let's go ahead and add another one with a class name of space y1 and then we're going to conditionally render the label so for now I'm just gonna write label here and add an else here to be null and let's now let's actually create this label so for that I'm going to use a Shatzion label component So go ahead and add npx chatcn-ui-at-latest-add-label wait for a second for this to be added to your project and there we go go back inside and now we can replace this div with a label which we can import again not from Radix but from slash UI label as you see right here but I don't like using it like that instead I'm gonna write components you can of course choose the one you like most great so now that we have this label let's add an equivalent close tag here and let's give it an HTML4 to be ID.
So these are going to be some attributes which are going to improve the accessibility and it's easy to do that here and it's worthwhile because this is a reusable component so we only have to do it once and our forms are going to be accessible throughout the entire project and let's give it a class name of text extra small font semi-bold text neutral 700 great and then let's go ahead and let's actually render the input component, which I just imported from ./.UIInput, but I'm gonna replace it with slash components. Input is going to be a self-closing tag. So go ahead and pass in the onBlur to be onBlur. Default value to be default value. We're also going to have the ref, which is going to be the ref.
Required, required. Name, which I'm just going to map to ID and it's also going to have the ID which is ID. Then we're going to give it a placeholder to be placeholder, we're going to give it a type to be type and let's go ahead and give it a disabled prop of either pending or manually disabled. And let's give it a class name which is going to be dynamic so let's go ahead and let's import a cm from add slash lib utils let's add the cm here let's give it the default classes which is text small px2 py1 and h7 and let's add the conditional class name which the user can pass in case they want to extend this and then let's add area-describedby to be id-error like that. Great and now what I want to do next is I want to create a reusable components for rendering our errors so let's go ahead and do that we're gonna do it first we're gonna assign it just inside of this component here so outside of this div which is wrapping our label and our input go ahead and render the form errors like this don't worry the fact that we have this error that it's not defined because we're going to create it now and go ahead and pass in the ID which is the ID and errors which is the errors.
Great and now let's go ahead and let's create a new file form-errors.tsx inside and let's go ahead and let's import XCircle from Lucid React. So whenever you're working with Lucid React, I've noticed that you can do both X Circle and you can always do X Circle icon, right? So if I try to import, let's try something M Square, you can also do M Square icon. So that's a cool little tip for you if you're ever wondering why you have different imports. So either x circle or x circle icon.
Let's create the interface form errors props. Let's give it an ID to be a string and errors is going to be an optional record which accepts the string as the first parameter and the array of strings or undefined as the second parameter and then export const form errors we get the ID and the errors. Let's give it a deformErrors. Props type here and let's go ahead and check if we don't have any errors. There is no need to render this component.
Otherwise, let's go ahead and let's return a div which is going to have an id of id dash error. It's going to have area dash live to be polite and we're gonna have a class name of MT2 text extra small text rows 500. Great and now inside let's go ahead and write errors question mark open parenthesis ID dot map again with a question mark here and let's go ahead and let's extract the individual error which is going to be a string and let's go ahead and render a div inside and let's just render that error let's give it a key of error and now let's just style it a bit so I'm gonna give it a class name of flex items center, font medium, padding 2, border, border rows 500 bg rows 500 slash 10 and rounded small. And then next to the actual error we're going to render the X circle from Lucid React. Sorry, we already have it imported, right?
Okay, so just one is enough. We don't need both variations. So X circle, make sure you have that. And let's just quickly give this some styles. So h-4, w-4 and margin right 2.
Perfect and now we can go back inside of the form input and we can import the form errors component from .formerrors and since they are in the same folder I'm not going to replace that with the alias because I think this is good enough. Great and now let's try this out. So now what I want to do is head back inside of our app folder platform dashboard organization organization ID and in here we have the form component which is currently using the form input from .forminput. So let's remove this import and let's remove this component form input inside of the organization ID page right you can enter it just confirm that it's not the component which you just created, right? So let's go ahead and remove that.
Let's go inside of this form now. And let's go ahead and add our form input from components form form input. There we go. So we have our new component here. Let me just align this stuff here.
Great, so make sure you have Form Input from s slash components, Form Form Input, which is the component we just worked on. And besides the errors, this one, as you can see, also needs to have an ID. So ID is title like that. And let's go ahead and let's see how this component looks like. So make sure you refresh your localhost.
There we go. And let's go ahead and write a text. And there we go. You can see how now we have custom errors here and when we're submitting you can see that it still has a little pending state but let's also test out the label right because that's another thing we added so let's give it a label to be board title for example oh and it looks like we are still rendering the hard-coded label so let's go ahead and fix that so inside of the components folder inside of form, form input. We have the label prop, but seems like we are not using it at all.
So label instead of the hard coded. And there we go. Now we have board title here. Great. And now what I want to test out is whether this is actually working because I don't think this should yield that the title is too short.
So let's go ahead and try that out. I'm gonna debug with you here. So let's go inside of the form right here and what first thing I'm gonna do is I'm going to console.log the title here and I like to do it inside of an object because it's just easier to find in the inspect element. So let me refresh this page, let me open my inspect element and let's write test and click submit and it looks like it is right here oh it looks like it's working great great great so maybe that was just some hot reload thing when I add a short one we have an error oh it's the error is not clearing because we have that throw error inside of our action. I think that's why.
Let me just go inside of our actions, create board. Yeah, we added an artificial error here, so let's remove the error. Alright, and let's try it out now. When I click submit, There we go. It looks like there is just this errors that's still showing up but we're going to work with that later.
We're going to see if it actually creates any problems or not for us. Great and now I want to do the similar thing but for the submit button. So I want to create a reusable submit button. So it's gonna be again similar to like this form button component, which we created here, right? There we go, this form button.
So let's go ahead and let me just close this stuff. Let's go inside of components, form, and let's create form button.psx. And first things first, let's mark this as useClient. And actually it's not gonna be called formButton, I wanna call it formSubmit. So that's what its purpose is going to be.
Otherwise we don't even need to use it, right? And let's go ahead and let's import useFormStatus from react-dom. Let's go ahead and let's import cn from lib-utils and let's import the actual button component from data slash ui-button or slash components ui-button. Let's create the interface form submit props. It's gonna have children which are type of react react node, disabled which is an optional boolean, last name which is a string and a variant which can be either default, destructive, outline, secondary, ghost, link or primary.
And let's export const form submit here. And let me just assign this props so form submit props and let's extract the children. Let's extract disabled, last name and variant. And let's open this arrow function here. And first thing I want to do is extract the pending state from use form status.
And then I'm just going to return a button component, which we imported and render the children inside. And then let's go ahead and assign all the necessary props. So disabled is gonna be pending or is manually disabled. Type is always going to be submit variant is going to be variant size is going to be small class name is going to be CN last name. So in case you have an error with this variant prop it probably means that you have a mismatch between what you wrote here and what the button actually accepts.
So just make sure that you don't have any typos here. You can always visit my GitHub or even better, we can go directly inside of the button component because it's right here in the UI folder. And in here you can see exactly what variants you have. Great, now let's go ahead and use this button. So make sure you save that and let's go inside of the app folder, platform, dashboard, organization, organization ID here.
Open up the form and let's already Delete form button, right? We're not going to need it here. Great. Go back inside of the form, remove this and let's go ahead and let's add form button. Maybe I forgot to export it.
Components, form. Oh, I named it form submit, of course, yeah. Form submit, there we go. So form submit from components, form, form submit. And let's just replace this form submit and let's give it a save text inside.
And I don't think we need to pass anything else. And this should work exactly as it was before, right? But it has built in pending status. Perfect. Great, great job.
So you just finished creating this reusable form components. What we're gonna do next is we're gonna create a popover which is gonna be triggered when we click on this plus icon here and then we're actually gonna be able to create our first board. And we're also going to learn how to connect to Unsplash API, so you can load those random images.