
No Comments Yet
Be the first to share your thoughts and start the conversation.
Be the first to share your thoughts and start the conversation.
Complete source code available till this point of lesson is available at
By logging in, you'll unlock full access to this and other free tutorials on JSM Pro.
Why? Logging in lets us personalize your learning experience, track your progress, and keep you in the loop with new workshops, coding tips, and platform updates.
You'll also be the first to know about upcoming launches, events, and exclusive discounts.
No spam—just helpful content to level up your skills.
If that sounds fair, go ahead and log in to continue →
Enter your name and email to get instant access
In this lesson, we explore how to create a reusable authentication form using Shadzian's React components, along with React Hook Form for state management and Zod for validation. The tutorial emphasizes building a scalable and functional form suitable for both sign-in and sign-up functionalities, and it walks through each step required to set up and customize the form accordingly.
mpx.shadzian.addLatest.addForm
).00:00:02 To create our Auth form, we'll use Shadzian's React component, which they pair very nicely with React Hook form and Zod validation.
00:00:11 And as they say, forms are tricky.
00:00:14 One of the most common things you'll build in a web app, but also one of the most complex.
00:00:19 So here they provide us with a quick guide of how we can create a proper form.
00:00:23 First things first, let's install a form component.
00:00:27 I'll head over to a second terminal and run mpx.shadzian.addLatest.addForm.
00:00:36 After that, we'll have to create a form schema.
00:00:38 So actually let's follow what they're doing step by step.
00:00:41 You can also pull up the Shadzian form documentation on the side.
00:00:44 I'll create a new component within forms and I'll call it AuthForm.
00:00:52 And within there, I'll start pasting the things that I copy from the docs, starting with the step one.
00:00:57 And here, they just tell us how we can define a schema for a specific form.
00:01:03 So we can go to the next step where we can define the actual form.
00:01:06 So let's copy this and override it.
00:01:09 Here, they define the schema fully, and then they create a new form.
00:01:13 They define the form using the use form, and they create an onSubmit handler for that form.
00:01:18 Finally, we need to build our form.
00:01:20 So let's copy all of these imports right here at the top.
00:01:24 and override the existing imports and then go down and copy the form component itself and paste it right here in a return statement,
00:01:33 return form.
00:01:35 There we go.
00:01:36 And they say, done, that's it.
00:01:38 You should have something that looks like this.
00:01:40 But of course, we'll take it step by step and I'll explain how everything works here.
00:01:45 First things first, let's actually install the input component so we can use it.
00:01:50 And I just noticed that the form is asking us whether we want to override the button.
00:01:54 Sure, let's do it.
00:01:56 And let's run mpx.shadcn.addLatest.addInput as well, because we will surely need it.
00:02:03 Now we can import this form within our sign-in and sign-up pages by heading over to Auth.signIn and then calling it right here.
00:02:12 Auth.form.
00:02:14 Oh, but we need to rename it first in the actual Auth.form because they called it profile form, but we'll actually say const Auth.form is equal to.
00:02:26 And then we can export default AuthForm at the bottom.
00:02:29 And now going back to the file, we can import AuthForm from components forms AuthForm, and then just declare it right here.
00:02:38 And we can repeat the same thing for the signup page.
00:02:42 So let's just fix over this return and import the AuthForm.
00:02:47 Now, if you go back to your application, you can see a simple form that says username, ChatCN, you can submit it, the validation works,
00:02:54 it is great.
00:02:55 But of course, now, I'll actually walk you step by step through what all of these things in this form component mean, how we can optimize them and further
00:03:05 improve them, and also how we can make this form reusable for both login and registration.
00:03:11 So let's take it step by step.
00:03:13 First things first, we have some imports from Zod, which we use for validation.
00:03:18 Then we have some more imports from React Hook Form, which we use to keep track of our form's state.
00:03:24 Finally, we're importing Schatzian components for buttons, forms, and inputs, and then we're defining the form schema.
00:03:31 We want to make this reusable and extensible.
00:03:34 So for now, we won't be using therefor schema.
00:03:38 Rather, we'll create our own validations.
00:03:41 So let's head over to our lib folder, which is right here in the root, and let's create a new file called validations.ts.
00:03:50 Within it, we can import z from Zod.
00:03:54 And then we can export const signInSchema is equal to z.object and you pass in a shape of that object.
00:04:05 So here you can define different fields that we'll have, such as email.
00:04:09 And email is going to be a z.string.
00:04:14 of a .min of one character with a custom message of email is required.
00:04:21 And we'll also give it a .email with a message of please provide a valid email address.
00:04:31 There we go.
00:04:32 So now this is how we're defining the values for our form, specifically defining the validation to make sure that our users have to enter the values of
00:04:40 a proper type.
00:04:42 We can do the same thing for the password by saying password is z dot string dot min six characters with a message saying password must be at least six
00:04:57 characters long.
00:04:58 And we can also do dot max of 100. with a message of password cannot exceed 100 characters.
00:05:07 Great!
00:05:08 So that's the validation for the sign-in.
00:05:10 Now, just so you don't have to retype all of this for the sign-up schema, below this lesson, I'll leave the validations for the sign-up schema.
00:05:17 You'll notice that it has the same email and password, but this time we also add the username and the name fields.
00:05:24 And in the password, we add some extra validation, saying that it must contain at least one uppercase character, at least one lowercase character,
00:05:33 at least one number, and at least one special character to make sure that our users are safe.
00:05:39 Now, let's head over to our sign-in page.
00:05:42 Within here, we'll want to tell this form, this one specifically, that it's going to act as the sign-in form.
00:05:49 So we can pass a form type prop, equal to sign in.
00:05:56 And alongside the form type, we'll also pass a schema, which is going to be equal to sign in schema, which we just created.
00:06:06 Since we're passing some special props to a client component, we have to turn this page into a client component as well.
00:06:12 So let's add a useClient directive at the top.
00:06:15 Alongside the form type and the schema, we want to further make this component reusable by passing it the default values,
00:06:23 which is going to be an object where we have an email of an empty string at the start and a password that's also an empty string.
00:06:31 Finally, we want to pass a function that's going to execute or tell us what happens on submit.
00:06:38 So it's going to be a callback function that gets the data.
00:06:41 and simply runs promise.resolve where it says success to be true and it passes over the data to the request.
00:06:51 So what are we even doing here?
00:06:53 Well, right now, our AuthForm is completely non-reusable.
00:06:57 It is a form that has its own values like a username, its own onSubmit function, and its own fields.
00:07:03 It's given the props so that it can act in many different roles, such as sign in or sign up.
00:07:10 We'll make it reusable.
00:07:11 So let's actually copy these props.
00:07:14 And head over to sign up, paste them into the auth form.
00:07:18 And don't forget to make it a use client component or page.
00:07:24 Change the form type to sign up, change the schema to sign up schema.
00:07:31 And we'll also have to pass it some additional fields since signups usually have more fields.
00:07:37 Like name is an empty string and username is an empty string as well.
00:07:43 And we can leave the onSubmit the same.
00:07:45 But you can still see that both forms are complaining about this form type that we're passing in.
00:07:51 That's because right now it doesn't yet know that it should be accepting anything.
00:07:56 It's just its own form.
00:07:58 So let's actually destructure the values right within it to tell it that it can actually do more than what it does right now.
00:08:05 Let's destructure the schema, the default values, the form type, and the onSubmit handler.
00:08:14 We can say that these are of a type AuthFormProps, and then T as in type right here.
00:08:22 So we have to define that because it's an interface AuthFormProps, T, which extends the field values coming from React Hook form,
00:08:36 because we want to make sure that this interface extends whatever fields we have.
00:08:41 So it's going to have a schema, which is of a type Zod type T.
00:08:47 We also have the default values, which are going to be T.
00:08:51 So the default values that we pass in, you can think of as T.
00:08:54 Then we have an onSubmit, which has the data, which is of a type T.
00:09:00 And it returns a promise, which returns a success of a type boolean.
00:09:06 And finally, we're passing the form type, which can be one of two strings, sign in or sign up.
00:09:17 There we go.
00:09:17 So now we know exactly what our function is accepting.
00:09:20 And we also have to say that this auth form extends the field values.
00:09:25 So we can say T extends field values.
00:09:29 That's going to look like this.
00:09:32 I know this is a bit more advanced TypeScript, but this is what you would typically find in Zod validation TypeScript examples,
00:09:40 as well as React hook form TypeScript examples.
00:09:43 So just bear with me.
00:09:45 Now we can define our form by saying const form is equal to use form z.infer typeof schema.
00:09:53 We have a resolver with the schema right here.
00:09:56 And then we need to pass the default values by saying default values.
00:10:02 as default values coming from React hook form to which we pass the t, which are the values that we pass into the actual form.
00:10:11 Next, we can remove this existing function, but we can create a new function for submit, which is going to be called const.
00:10:18 handleSubmit of a type SubmitHandler that has the values of t, which is equal to an async function, which for now we can leave just empty like this.
00:10:30 And we'll have to use this handleSubmit and pass it here instead of the onSubmit.
00:10:34 Next, let's define the button text, and this will help us to figure out on which form we're on.
00:10:39 So we can say const buttonText and if formType is triple equal to sign underscore in, then the button will say sign in, else the button will say sign up
00:10:56 like this.
00:10:58 So for now, I will just render it right here at the top of the form, button text, and you can see that right now we're in the sign in form.
00:11:07 At the same time, if I head over to Sign Up, then you can see that we're on the Sign Up form.
00:11:14 And we do get one error.
00:11:16 It's a hydration error, which we'll fix later.
00:11:18 But now the question is, how can we render different fields depending on different forms?
00:11:24 Well, for that, we'll use the default values that we're passing to both Sign In and Sign Up forms.
00:11:30 We want to map over this object and display a new field for each one of these.
00:11:36 Let me show you how we'll do that.
00:11:39 You can open up a new dynamic block and say object dot keys, and then pass in the default values.
00:11:47 This will allow us to get just names of different inputs like email, username, and so on.
00:11:53 Then you want to map over these inputs by saying dot map field.
00:11:58 And for each field, you want to return what?
00:12:02 Well, you want to return a form field.
00:12:04 So let's put this form field one line above.
00:12:08 So it fits right here.
00:12:10 And now you can see four different username inputs.
00:12:14 But instead of having four different username inputs, we can actually use this field to display proper values.
00:12:20 So we can give it a key equal to field so it knows that we're mapping over it.
00:12:25 We can give it a name equal to field as path t and this path is coming from react hook form basically telling us that it's going to be one of these default
00:12:37 values that we have after that we are rendering a form item right here under form label we can check if the field dot name is triple equal to email in
00:12:49 that case i'll simply say email address But in every other case, I'll render the field.name.
00:12:59 So now you can see email address, password, name, and username.
00:13:03 But I don't like that this is lowercase right here, so we should figure out how to uppercase the first letter.
00:13:10 We can do that with a bit of JavaScript magic by saying field.name.car at, so character at, position of zero, that's the first character,
00:13:22 .to uppercase, And then plus field.name.slice from one onward, which means uppercase the first one and then return all the other ones.
00:13:34 Great.
00:13:35 Now we can also see that the placeholder value for all of these is just ShadCN.
00:13:39 And it says, this is your public display name for all of them.
00:13:43 Let's fix that.
00:13:44 We can do it by fixing up this input right here.
00:13:49 We can give it a property of required, a type.
00:13:53 If it's field dot name, and if it's equal to password, then we want to change it over to a password field.
00:14:01 So the users cannot see what this user is typing.
00:14:04 Else we'll make it a text field.
00:14:06 Then we want to spread over all of the other field properties and give it a class name.
00:14:11 Paragraph dash regular.
00:14:15 background-light-900, underscore dark 300, light-border-2, text-dark 300, underscore light 700, no-focus, min-h-12, rounded-1.5,
00:14:37 and border.
00:14:38 If we hover over it, you can see that background light 900, dark 300 doesn't exist.
00:14:43 That's because I have an extra dash right here.
00:14:46 So it's pretty cool how it saved me there.
00:14:48 And now we have a bit more customized fields.
00:14:50 We can also remove this form description.
00:14:54 There we go.
00:14:54 That's more like it.
00:14:55 Now let's style the rest of the form field or the form item by giving it a class name equal to flex w-full flex-call and the gap of 2.5 between the elements.
00:15:11 Oh, it looks like I have double L in the call.
00:15:14 There we go.
00:15:14 That fixes it.
00:15:16 Let's also style the label by giving it a class name of paragraph-medium Text dash dark 400, light 700. This is looking great.
00:15:28 And let's also style the form itself by giving it a margin top of 10 and space dash y dash 6 to divide it a bit from the top title right here.
00:15:40 And take a look at this.
00:15:41 We have a beautiful form.
00:15:43 We can also further style this button by giving it a disabled property.
00:15:48 And we want to make it disabled if form.formState.isSubmitting is currently on.
00:15:56 It's pretty cool how you're immediately getting this isSubmittingState just by using React Hookform.
00:16:02 In this case, we don't need to make it submit.
00:16:04 And we can style it by giving it a class name of primary-gradient.
00:16:09 That's immediately better.
00:16:11 paragraph-medium to make the text a bit larger.
00:16:15 min-h-12 for the height, w-full for the width, rounded-2 padding X of four, padding Y of three, font dash enter, and exclamation mark text dash light dash 900.
00:16:35 We use exclamation mark when we want to make sure that we override some of the default ShadCN properties on the button.
00:16:42 So this is looking great.
00:16:43 We can also change the button text based on the isSubmittingState.
00:16:48 So if form.formState.isSubmitting, Then we also want to check the button text.
00:16:57 If button text is triple equal to sign in, then we want to return signing in, dot dot dot.
00:17:05 Else we want to return signing up, dot dot dot.
00:17:11 else we want to return the button text which is just going to say sign in or sign up so as you can see now it says sign up because we're in the sign up
00:17:20 form and finally just so we don't have to mess with the URL bar let's go below the button and let's make a link that will allow us to move between the
00:17:28 sign in and the sign up forms we can do that by first checking the type of the form so let's say form type is triple equal to sign underscore n and if
00:17:39 it is we can return a p tag else we can also return a p tag currently we are on the sign up so let's focus on this second part of the ternary where i'm
00:17:50 gonna say already have an account And then we can create an empty space right within it to divide it a bit from the link.
00:17:59 And then right after that, we can render a Next.js link component that's going to say sign in, and we can give it an href pointing to routes.
00:18:10 dot sign underscore in and a class name of paragraph dash semi bold primary dash text dash gradient and there we go already have an account sign in and
00:18:24 now I'm gonna duplicate this p tag paste it right here below and this one will say something different it'll say don't have an account question mark Then
00:18:35 we're going to route to sign underscore up, and here we can say sign up.
00:18:42 And check this out.
00:18:43 We have a beautiful form with a title and then social auth buttons and the entire form in the background.
00:18:51 which is coming from the layout.
00:18:52 So these things are always static.
00:18:55 You can see how quickly it loads.
00:18:57 We also have the possibility to move in between those two different forms.
00:19:01 But then we have created a reusable AuthForm component that dynamically renders the fields based on the schema that we pass into it.
00:19:10 So on sign up, we have email, password, name, and username.
00:19:13 And on sign in, we have the email and the password.
00:19:16 And it is looking great both on desktop We can also go on smaller devices such as tablets, and finally on the phone, it is looking like it's made for the phone,
00:19:26 basically.
00:19:26 Great.
00:19:28 So now we can go a bit up, and there's one thing that remains for us to do here, and that is to actually authenticate the user.
00:19:37 So I can add a to-do in the handle submit and say authenticate user.
00:19:43 We'll do this soon.
00:19:45 But for now, I can clean up this unused form schema, which we're not using because now we have a dynamic one, as well as this form description,
00:19:53 which we don't need.
00:19:55 and I can take my time to explain one more time what is happening here.
00:20:00 So everything starts with our Auth layout, where we're displaying the shared things among those two pages, like the SocialAuth form,
00:20:09 as well as the background image, and the main layout with the form.
00:20:12 Then we have two different pages, the Sign In page, and the Sign Up page, which render a form.
00:20:20 But now what we have done in a smart way is we have made this form completely reusable, which allows us to pass the schema,
00:20:28 default values, and the form type, which we're passing differently for both of these forms.
00:20:34 And based on these dynamic pieces of data, we're now rendering specific fields and inputs on the form to accommodate those differences.
00:20:42 So, great job on completing the UI part of the form.
00:20:46 We might do the authentication for the logic next, or maybe we're gonna do it a bit later, just to focus on some other stuff and not just the form.
00:20:53 But in any case, I want you to take a bit of a break now.
00:20:57 Go for a walk, eat something, drink something, or just stand up from your computer.
00:21:01 You've done a phenomenal job so far, and you deserve a break.
00:21:04 So, I can't wait to see you in the next lesson.