Building a Form Validation System with Fluent UI and Zod in TypeScript

Form validation is a critical aspect of web development, ensuring that user inputs are accurate and meet the required criteria before submission. Effective form validation enhances user experience by providing immediate feedback and preventing errors. In this blog post, we’ll explore how to implement form validation in a React application using Fluent UI components and the Zod validation library. I will use TypeScript for all examples to ensure type safety and robust code. Our example will involve a user registration form with fields for username and email.

Thank me by sharing on Twitter 🙏

Setting Up Fluent UI and Zod

To begin, ensure you have @fluentui/react and zod installed in your project. Fluent UI provides a comprehensive set of components that follow Microsoft’s design language, while Zod is a TypeScript-first schema declaration and validation library.

ShellScript
npm install @fluentui/react zod

In our project, i’ll create a form component called UserPage that will handle user input and validation. This form will include fields for username and email, and it will display appropriate error messages if the inputs do not meet the validation criteria defined by Zod.

Defining the Zod Schema

The first step in our validation process is defining the schema using Zod. The schema will specify the rules for each form field.

TSX
import { z } from 'zod';

// Define Zod schema

const schema = z.object({
  username: z.string()
    .nonempty({ message: 'Username is required' }),
  email: z.string()
    .nonempty({ message: 'Email is required' })
    .email({ message: 'Invalid email address' }),
  });

The schema ensures that the username is a non-empty string and that the email is both non-empty and follows the correct email format.

Creating the UserPage Component

Next, create the UserPage component. This component will manage form state, handle user input, and validate the inputs against the Zod schema.

TSX
import React, { useState } from 'react';
import { Input, Field, Button } from '@fluentui/react';
import { z } from 'zod';

// Define Zod schema
const schema = z.object({
  username: z.string().nonempty({ message: 'Username is required' }),
  email: z
    .string()
    .nonempty({ message: 'Email is required' })
    .email({ message: 'Invalid email address' }),
});

const UserPage = () => {
  const [formValues, setFormValues] = useState({ username: '', email: '' });
  const [formErrors, setFormErrors] = useState({ username: '', email: '' });

  const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    const { name, value } = e.target;
    setFormValues({
      ...formValues,
      [name]: value,
    });
  };

  const handleSubmit = (e: React.FormEvent) => {
    e.preventDefault();

    const result = schema.safeParse(formValues);

    if (!result.success) {
      const errors = result.error.flatten().fieldErrors;
      setFormErrors({
        username: errors.username ? errors.username[0] : '',
        email: errors.email ? errors.email[0] : '',
      });
    } else {
      setFormErrors({ username: '', email: '' });
      console.log('Form Submitted', formValues);
    }
  };

  return (
    <form onSubmit={handleSubmit}>
      <Field label="Username" validationMessage={formErrors.username}>
        <Input
          name="username"
          value={formValues.username}
          onChange={handleChange}
        />
      </Field>

      <Field label="Email" validationMessage={formErrors.email}>
        <Input
          name="email"
          value={formValues.email}
          onChange={handleChange}
        />
      </Field>

      <Button appearance="primary">
        Submit
      </Button>
    </form>
  );
};

export default UserPage;

Handling Form State

The UserPage component maintains form state using the useState hook. It has two state variables: formValues for storing the current input values and formErrors for storing any validation errors.

TSX
const [formValues, setFormValues] = useState({ username: '', email: '' });
const [formErrors, setFormErrors] = useState({ username: '', email: '' });

The handleChange function updates the form values state whenever the user types into an input field.

const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
  const { name, value } = e.target;
  setFormValues({
    ...formValues,
    [name]: value,
  });
};

Validating Inputs

When the form is submitted, the handleSubmit function validates the inputs against the Zod schema using safeParse.

TSX
const handleSubmit = (e: React.FormEvent) => {
  e.preventDefault();

  const result = schema.safeParse(formValues);

  if (!result.success) {
    const errors = result.error.flatten().fieldErrors;
    setFormErrors({
      username: errors.username ? errors.username[0] : '',
      email: errors.email ? errors.email[0] : '',
    });
  } else {
    setFormErrors({ username: '', email: '' });
    console.log('Form Submitted', formValues);
  }
};

If validation fails, safeParse returns an error object which is then used to set the formErrors state. If validation is successful, the form errors are cleared, and the form values are logged to the console.

Rendering the Form

The form fields are rendered using Fluent UI’s Input and Field components. The validationMessage prop of the Field component is used to display validation errors.

TSX
return (
  <form onSubmit={handleSubmit}>
    <Field label="Username" validationMessage={formErrors.username}>
      <Input
        name="username"
        value={formValues.username}
        onChange={handleChange}
      />
    </Field>

    <Field label="Email" validationMessage={formErrors.email}>
      <Input
        name="email"
        value={formValues.email}
        onChange={handleChange}
      />
    </Field>

    <Button appearance="primary">
      Submit
    </Button>
  </form>
);

Conclusion

Implementing form validation in a React application using Fluent UI and Zod is a straightforward process that ensures robust and type-safe forms. By defining a schema with Zod and managing form state manually, you can provide immediate feedback to users and prevent errors before form submission. This approach not only improves user experience but also helps maintain the integrity of the data being collected.

In this blog post, we’ve walked through the setup and implementation of a simple user registration form with validation. By leveraging the power of TypeScript, Fluent UI, and Zod, you can create highly maintainable and user-friendly forms in your React applications.

Share this:

Leave a Reply