Creating a Login Form with MUI and Formik in a React Application – SmaRF Tech Knowledge Base

Last month, we started a SmaRF Knowledge Base by writing a blog about FluentValidation. In this article, we’ll demonstrate how to create a login form in a React application using the Material User Interface (MUI) and Formik libraries.

What are MUI and Formik and what are they used for?

MUI is a library that provides a wide range of ready-to-use UI components for React applications. It is easy to use in production and has a clean, modern design. 

Formik is the most popular open source form library for React, and it allows us to easily handle form state, validation, and submission.

Let’s take a look at our sandbox code

We’ll use MUI components for the visible elements of the form, such as the TextField and Checkbox components for the input fields and the Button component for the Submit button. Then, we’ll use Formik hooks to change the values of the input fields and handle form validation.

Visit our sandbox to see the full code.

To define data types, we use Typescript whenever possible. For input fields, we were using the TextField component and for the checkbox – Checkbox component. We are using the Typography component for the login header label and the Button component for the Submit button.

The useFormik() hook and its use

To explain our sandbox code in detail (perhaps you’ll want to keep it open in the separate tab), we’ll start from a useful React hook.

useFormik() is a custom React hook that will return all Formik states and helpers directly. Our Formik form definition looks like this:

const loginFormikForm = useFormik({
        initialValues: {
                email: "",
	        password: "",
	        keepLoggedIn: false
        } as ILoginValues,
validationSchema: loginValidationSchema,
onSubmit: () => handleSubmitForm(loginFormikForm)
});

The first property inside the form definition is the initialValues property. This property defines names and initial values for the form properties (email, password and keepLoggedIn)

To define the data type for each property, we are using ILoginValues interface. This looks like this:

interface ILoginValues {
	email: string;
	password: string;
	keepLoggedIn: boolean;
}

The second property is the validationSchema and it has the loginValidationSchema constant assigned to it.

With the validationSchema, you can provide restrictions that are being used for data validation. When creating the restrictions, you can use the Yup library. Yup is a JavaScript schema builder for value parsing and validation.

So now our validation schema looks like this: 

const loginValidationSchema = () => {
	return Yup.object({
		email: Yup.string()
		  .email("Invalid email address")
		  .required("This field is required"),
		password: Yup.string()
		  .required("This field is required")
		  .max(
			MAXIMUM_PASSWORD_LENGTH,
			`Maximum password length is ${MAXIMUM_PASSWORD_LENGTH} characters`
		  )
		  .min(
			MINIMUM_PASSWORD_LENGTH,
			`Minimum password length is ${MINIMUM_PASSWORD_LENGTH} characters`
		  )
	});
};

The constant loginValidationSchema is returning a Yup object. Inside of it there’s a definition for the email and password fields. Now, we don’t have to add validation for the keepLoggedIn property.

Yup – definitions, validations and properties

As you can see in the sandbox, the first validation definition is the definition for the email property. The name of the field needs to be the same as the one in the initialValues object.

After defining the name, we assign the Yup.string() function to this field. With this function, we define the type of data that is going to be provided to this property in the schema.

After the type definition, you can use the .email method, also provided by Yup. With this method, Yup will trigger its own functions for email validation, which allows us to focus on other things.

Validation of the email field is complex. Perhaps the best advice is to leave email validation to the Yup library. Writing your own library is fine, too, but sometimes it’s better to use ready-made tools since time is of the essence in this industry.

After the .email method, there’s the .required() method

As you can see from the method name, the .required() method defines that the email property is required (it can’t be empty). We can define our own validation message that will be shown if validation is triggered.

If you leave these validation messages empty, the default message will be displayed.

for .email: "email must be a valid email"
for .required: "email is a required field"

The second validation definition for the password field

The password field is also required, so you have to call the .required method again. Beside the .required method, you could also use the .max and .min methods.

First, you have to define the value for the minimum or maximum length of the password.

In our sandbox, we are using constant variables that are defined on top of the file to define restrictions for the password length. 

const MAXIMUM_PASSWORD_LENGTH = 30;
const MINIMUM_PASSWORD_LENGTH = 6;

This allows us to easily change this value if e.g. a client wants to change restrictions of validation. This is also useful if you wish to avoid the so-called magic numbers.

After providing the values for the maximum and minimum length of the password field, you could also insert a custom message that appears if this validation is triggered.

Of course, as with the .email method, if you don’t provide any messages, Yup’s default message will be displayed:

maximum: "password must be at most 30 characters"
minimum: "password must be at least 6 characters"

The onSubmit function

The third property is the onSubmit function that will be called if the validation goes well. For the purposes of this app, we’ve created a basic function that will alert us about values of our properties.

This function looks like this:

const handleSubmitForm = (loginFormikForm) => {
	const { email, password, keepLoggedIn } = loginFormikForm.values;
	alert(
	  `email: ${email}\npassword: ${password}\nkeepLoggedIn: ${keepLoggedIn}`
	);
};

To get our values from the form values, we are using the Destructuring Method.

After getting the data, we are simply alerting them to the user. In most cases, we’ll send it to a database.

How to connect the MUI components and the Formik validation

All our MUI components are set inside HTML form tag:

<form onSubmit={loginFormikForm.handleSubmit}>

We have onSubmit defined for the form tag and it has our handleSubmit function defined to be triggered on the form submission.

This onSubmit function will be triggered after we click on the Submit button that we have defined on the bottom of this form.

This button looks like this:

<Button variant="contained" type="submit">
Submit
</Button>

Two thing are important here: 

1) This button element needs to be inside the form tag

2) The type of this button needs to be “submit” (<Button variant=”contained” type=”submit”>)

Before triggering the submit function, we have to populate the input fields to satisfy the validation rules. Both of our input fields are defined in the same way so we are going to explain just one of them – the email field.

The definition of the email input field looks like this:

<TextField
            id="email"
            name="email"
            label="Email"
            type="email"
            placeholder="Email Address"
            value={loginFormikForm.values.email}
            onChange={loginFormikForm.handleChange}
            error={
              loginFormikForm.touched.email &&
              Boolean(loginFormikForm.errors.email)
            }
            helperText={
              loginFormikForm.touched.email && loginFormikForm.errors.email
            }
            margin="dense"
/>

We are using the MUI’s TextField. The name property needs to have the same value as the one inside definition and validation schema, so that Formik could handle the data change inside these fields and trigger validations.

We could also provide value for a label and avoid the need for creating a separate label HTML element and connecting it to the input field. This makes our job easier.

We have also defined the type of this field (type=”email”). With this property, the browser can easily find the type of our field and trigger some of its own functions for validation or auto-population.

The Value property

This property has its value assigned to the form value property. It saves changed values inside the input field when the data is changed. The onChange property is calling form’s handleChange function that is provided by Formik.

This function automatically assigns changed values from input field to its property defined in the form initialValues definition and triggers its validation on submit.

This also explains why the name property is so important. In all cases above, the name property needs to be the same so Formik could handle the change and validation of data.

The error boolean property

This property checks if the field is touched (entered) and if there are any errors triggered inside the validation schema. The helperText property shows error messages that we have already defined in the validation schema.

This message is displayed only if the error property is set to true. If we populate the input fields with valid data, the message is not going to be displayed anymore.

Did you like our guide on MUI and Formik?

We’ve walked through the process of creating a login form in a React application using the MUI and Formik libraries. We’ve covered creating the form, validating form input, and handling form submission.

We hope this tutorial has been helpful and that you feel more confident in creating your own login forms using these tools. If you have any further questions or need more guidance, be sure to check out the MUI and Formik documentation.

And don’t forget to check out our other tutorials and resources in the knowledge base for more tips and tricks on building web applications with React and other technologies. Thank you for reading!

Leave a Reply

Your email address will not be published.