🖍️
Developer Note
  • Welcome
  • Git
    • Eslint & Prettier & Stylelint & Husky
  • Programming Language
    • JavaScript
      • Script Async vs Defer
      • Module
      • Const VS Let VS Var
      • Promise
      • Event Loop
      • Execution Context
      • Hoisting
      • Closure
      • Event Buddling and Capturing
      • Garbage Collection
      • This
      • Routing
      • Debounce and Throttle
      • Web Component
      • Iterator
      • Syntax
      • String
      • Array
      • Object
      • Proxy & Reflect
      • ProtoType
      • Class
      • Immutability
      • Typeof & Instanceof
      • Npm (Node package manager)
    • TypeScript
      • Utility Type
      • Type vs Interface
      • Any vs Unknown vs Never
      • Void and undefined
      • Strict Mode
      • Namespace
      • Enum
      • Module
      • Generic
    • Python
      • Local Development
      • Uv
      • Asyncio & Event loop
      • Context Manager
      • Iterator & Generator
      • Fast API
      • Pydantic & Data Class
    • Java
      • Compilation and Execution
      • Data Type
      • Enumeration
      • Data Structure
      • Try Catch
      • InputStream and OutputStream
      • Concurrent
      • Unicode Block
      • Build Tools
      • Servlet
      • Java 8
  • Coding Pattern
    • MVC vs MVVM
    • OOP vs Functional
    • Error Handling
    • MVC vs Flux
    • Imperative vs Declarative
    • Design Pattern
  • Web Communication
    • REST API
      • Web Hook
      • CORS issue
    • HTTPS
    • GraphQL
      • REST API vs GraphQL
      • Implementation (NodeJS + React)
    • Server-Sent Event
    • Web Socket
    • IP
    • Domain Name System (DNS)
  • Frontend
    • Progressive Web App (PWA)
    • Single Page & Multiple Page Application
    • Search Engine Optimiaztion (SEO)
    • Web bundling & Micro-frontend
      • Webpack
        • Using Webpack to build React Application
        • Using Webpack to build react library
      • Vite
      • Using rollup to build react library
      • Implementing micro frontend
    • Web Security
      • CSRF & Nonce
      • XSS
      • Click hijacking
    • Cypress
    • CSS
      • Core
        • Box Model
        • Inline vs Block
        • Flexbox & Grid
        • Pseudo Class
        • Position
      • Tailwind CSS
        • Shadcn
      • CSS In JS
        • Material UI
    • React
      • Core
        • Component Pattern
        • React Lazy & Suspense
        • React Portal
        • Error Boundary
        • Rendering Methods
        • Environment Variable
        • Conditional CSS
        • Memo
        • Forward Reference
        • High Order Component (HOC) & Custom Hook
        • TypeScript
      • State Management
        • Redux
        • Recoil
        • Zustand
      • Routing
        • React Router Dom
      • Data Fetching
        • Axios & Hook
        • React Query
        • Orval
      • Table
        • React Table
      • Form & Validation
        • React Hook Form
        • Zod
      • NextJS
        • Page Router
        • App Router
      • React Native
    • Angular
    • Svelte
      • Svelte Kit
  • Backend
    • Cache
      • Browser Cache
      • Web Browser Storage
      • Proxy
      • Redis
    • Rate limit
    • Monitoring
      • Logging
      • Distributed Tracing
    • Load Test
    • Encryption
    • Authentication
      • Password Protection
      • Cookie & Session
      • JSON Web Token
      • SSO
        • OAuth 2.0
        • OpenID Connect (OIDC)
        • SAML
    • Payment
      • Pre-built
      • Custom
    • File Handling
      • Upload & Download (Front-end)
      • Stream & Buffer
    • Microservice
      • API Gateway
      • Service Discovery
      • Load Balancer
      • Circuit Breaker
      • Message Broker
      • BulkHead & Zipkin
    • Elastic Search
    • Database
      • SQL
        • Group By vs Distinct
        • Index
        • N + 1 problem
        • Normalization
        • Foreign Key
        • Relationship
        • Union & Join
        • User Defined Type
      • NOSQL (MongoDB)
      • Transaction
      • Sharding
      • Lock (Concurrency Control)
    • NodeJS
      • NodeJS vs Java Spring
      • ExpressJS
      • NestJS
        • Swagger
        • Class Validator & Validation Pipe
        • Passport (Authentication)
      • Path Module
      • Database Connection
        • Integrating with MYSQL
        • Sequalize
        • Integrating with MongoDB
        • Prisma
        • MikroORM
        • Mongoose
      • Streaming
      • Worker Thread
      • Passport JS
      • JSON Web Token
      • Socket IO
      • Bull MQ
      • Pino (Logging)
      • Yeoman
    • Spring
      • Spring MVC
      • Spring REST
      • Spring Actuator
      • Aspect Oriented Programming (AOP)
      • Controller Advice
      • Filter
      • Interceptor
      • Concurrent
      • Spring Security
      • Spring Boot
      • Spring Cloud
        • Resilience 4j
      • Quartz vs Spring Batch
      • JPA and Hibernate
      • HATEOS
      • Swagger
      • Unit Test (Java Spring)
      • Unit Test (Spring boot)
  • DevOp
    • Docker
    • Kubernetes
      • Helm
    • Nginx
    • File System
    • Cloud
      • AWS
        • EC2 (Virtual Machine)
        • Network
        • IAM
          • Role-Service Binding
        • Database
        • Route 53
        • S3
        • Message Queue
        • Application Service
        • Serverless Framework
        • Data Analysis
        • Machine Learning
        • Monitoring
        • Security
      • Azure
        • Identity
        • Compute Resource
        • Networking
        • Storage
        • Monitoring
      • Google Cloud
        • IAM
          • Workload Identity Federation
        • Compute Engine
        • VPC Network
        • Storage
        • Kubernetes Engine
        • App Engine
        • Cloud function
        • Cloud Run
        • Infra as Code
        • Pub/Sub
    • Deployment Strategy
    • Jenkins
    • Examples
      • Deploy NextJS on GCP
      • Deploy Spring on Azure
      • Deploy React on Azure
  • Domain Knowledge
    • Web 3
      • Blockchain
      • Cryptocurrency
    • AI
      • Prompt
      • Chain & Agent
      • LangChain
      • Chunking
      • Search
      • Side Products
Powered by GitBook
On this page
  • Form
  • Component

Was this helpful?

  1. Frontend
  2. React
  3. Form & Validation

React Hook Form

PreviousForm & ValidationNextZod

Last updated 6 months ago

Was this helpful?

Form

  • Mainly responsible for defining form submitting function, the type of form data, validation rule of each component, pass the props to component through form context provider

  • List of validation rules supported:

    • required

    • min

    • max

    • minLength

    • maxLength

    • pattern => regExp

    • validate => using self-defined function to validate

  • To support schema-based form validation with , , & , where you can pass your schema to as an optional config. It will validate your input data against the schema and return with either or a valid result.

import React from "react";
import { FormProvider, useForm } from "react-hook-form";
import { Form } from "react-aria-components";
import CustomTextField from "../forms/customTextField";
import CustomButton from "../forms/customButton";
import useLoginMutation from "../../hooks/login/useLoginMutation";
import { useNavigate } from "@tanstack/react-router";
import { zodResolver } from "@hookform/resolvers/zod";
import { LoginRequest, loginRequestSchema } from "../../types/login";

const defaultFormValues: LoginRequest = {
  email: "",
  password: "",
};

const Login = () => {
  const { mutateAsync } = useLoginMutation();
  const navigate = useNavigate();
  const methods = useForm({
    defaultValues: defaultFormValues,
    resolver: zodResolver(loginRequestSchema),
  });

  const { handleSubmit, setError } = methods;

  const onSubmit = async (data: LoginRequest) => {
    try {
      const user = await mutateAsync(data);
      localStorage.setItem("user", user.id.toString());
      navigate({ to: "/" });
    } catch (err) {
      if (err instanceof Error) {
      // set back the error to the field
        setError("email", { message: err.message });
      }
    }
  }
  // to have side effect when failed to submit
  const onError = (input: unknown) => {
    console.log(input);
  };
  
  return (
    <FormProvider {...methods}>
      <Form
        className="h-full flex flex-col items-center justify-center gap-2"
        onSubmit={(e) => {
          e.preventDefault();
          // for handle submit, the validation will be triggered when the data is
          // firstly submitted or changed
          handleSubmit(onSubmit, onError)();
        }}
      >
        <CustomTextField
          name="email"
          rules={{
            required: true,
          }}
          label={"Email"}
        />
        <CustomTextField
          name="password"
          type="password"
          rules={{ required: true }}
          label={"Password"}
        />
        <CustomButton label="Login" type="submit" className="mt-5" />
      </Form>
    </FormProvider>
  );
};

export default Login;

Component

  • Component is mainly responsible to handle field status correctly and the change behaviour

  • controlis passed into component with provider by default and linked with the parent form, so that the component becomes controllable

  • useControllerreturn the field status (isDirty, error) and on change method, etc

import { Label, TextField, Input, FieldError } from "react-aria-components";
import { FieldValues, useController } from "react-hook-form";

export default function CustomTextField({
  label,
  control,
  name,
  rules,
  defaultValue,
  ...props
}: FieldValues) {
  const { field, fieldState } = useController({
    name,
    control,
    rules,
    defaultValue,
  });
  return (
    <TextField
      isInvalid={!!fieldState.error}
      {...field}
      {...props}
      className="flex flex-col gap-2"
    >
      <Label className="text-xl">{label}</Label>
      <Input className="h-9 w-80 bg-slate-500 text-lg rounded-sm text-white" />
      <FieldError className={"text-red-400"}>
        {fieldState.error?.message
          ? fieldState.error.message
          : "The field is required"}
      </FieldError>
    </TextField>
  );
}
  • For array of field case, using useFieldArray hook to handle

  • Return the values of array and append, remove method

import {
  FieldValues,
  useController,
  useFieldArray,
  useFormContext,
} from "react-hook-form";
import CustomSwitch from "./customSwitch";
import { Option } from "../../types/common";

interface Props extends FieldValues {
  options: Option[];
  isReadOnly?: boolean;
}

const CustomSwitchGroup = ({
  control,
  name,
  options,
  isReadOnly = false,
}: Props) => {
  const { remove, append } = useFieldArray({
    name,
    control,
  });
  const { watch, getValues } = useFormContext();
  const { fieldState } = useController({
    name,
    control,
  });
  return (
    <>
      {options.map((option) => (
        <CustomSwitch
          isSelected={watch(name).includes(option.key)}
          onChange={(value) => {
            const optionsSelected: number[] = getValues(name);
            if (!value) {
              const index = optionsSelected.findIndex(
                (optionKey) => optionKey === option.key,
              );
              remove(index);
            } else {
              if (!optionsSelected.includes(option.key)) {
                append(option.key);
              }
            }
          }}
          label={option.name}
          key={option.key}
          isReadOnly={isReadOnly}
        />
      ))}
      {fieldState.error && (
        <span className={"text-red-400"}>
          {fieldState.error?.message
            ? fieldState.error.message
            : "The field is required"}
        </span>
      )}
    </>
  );
};

export default CustomSwitchGroup;
import * as React from "react";
import { useForm, useFieldArray, useWatch } from "react-hook-form";

export default function App() {
  const { control, handleSubmit } = useForm();
  const { fields, append, update } = useFieldArray({
    control,
    name: 'array'
  });

  return (
    <form onSubmit={handleSubmit((data) => console.log(data))}>
      {fields.map((field, index) => (
        <Edit
          key={field.id}
          control={control}
          update={update}
          index={index}
          value={field}
        />
      ))}

      <button
        type="button"
        onClick={() => {
          append({ firstName: "" });
        }}
      >
        append
      </button>
      <input type="submit" />
    </form>
  );
}

const Display = ({ control, index }) => {
  const data = useWatch({
    control,
    name: `array.${index}`
  });
  return <p>{data?.firstName}</p>;
};

const Edit = ({ update, index, value, control }) => {
  const { register, handleSubmit } = useForm({
    defaultValues: value
  });

  return (
    <div>
      <Display control={control} index={index} />
      
      <input
        placeholder="first name"
        {...register(`firstName`, { required: true })}
      />

      <button
        type="button"
        onClick={handleSubmit((data) => update(index, data))}
      >
        Submit
      </button>
    </div>
  );
};
Yup
Zod
Superstruct
Joi
useForm
errors