iT邦幫忙

2021 iThome 鐵人賽

DAY 13
0
Modern Web

用30天更加認識 React.js 這個好朋友系列 第 13

Day13-React 表單驗證篇-使用第三方函式庫 Formik 進行表單的驗證

Formik 介紹

在進行實作之前,先來認識一下 Formik 吧~

如標題所說,Formik 是一個表單函式庫,而且還是 React 官方推薦的,相似的還有 Redux Form、React Final Form 等。並且 Formik 還可以搭配 Yup 撰寫驗證規則、訊息。

透過這些第三方的表單函式庫可以幫助我們處理 Input 的事件追蹤驗證 Input value透過 React Context 管理 Form 的 state避免許多重複性值的程式碼

Formik 的官網連結

實作時間

現在我們要來用 React 和 Formik 建立一個表單出來。

本篇教學從 Formik 官網文件的教學部分 改寫而來

1. 簡單做一個表單出來

const SimpleForm = () => {
  return (
    <form>
      <label htmlFor="name">Your Name</label>
      <input type="text" id="name" />
      <label htmlFor="email">Your E-Mail</label>
      <input type="email" id="email" />
      <button type="submit">Submit</button>
    </form>
  );
};

2. 使用 useFormik hook

透過這個 hook 去管理表單的 state 和一些函式。

import { useFormik } from "formik";

const SimpleForm = () => {
  const formik = useFormik({
    initialValues: {
      name: "",
      email: ""
    },
    onSubmit: (values) => {
      console.log(values);
    }
  });

  return (
    <form onSubmit={formik.handleSubmit}>
      <label htmlFor="name">Your Name</label>
      <input
        type="text"
        id="name"
        name="name"
        onChange={formik.handleChange}
        value={formik.values.name}
      />
      <label htmlFor="email">Your E-Mail</label>
      <input
        type="email"
        id="email"
        name="email"
        onChange={formik.handleChange}
        value={formik.values.email}
      />
      <button type="submit">Submit</button>
    </form>
  );
};

3. 加上驗證函式和錯誤訊息

上一個步驟我們是可以直接送出表單看到值,現在我們針對表單輸入內容進行驗證,因此建立一個驗證函式 validate。

將這個驗證函式加入到 useFormik 的參數物件內,會在每次觸發 onChange 和 onBlur 事件時做驗證。

在後面的步驟,會改用 Yup 做驗證

完成的表單元件如下:

import { useFormik } from "formik";

import "./styles.css";

const emailRule = /^\w+((-\w+)|(\.\w+))*\@[A-Za-z0-9]+((\.|-)[A-Za-z0-9]+)*\.[A-Za-z]+$/;

const validate = (values) => {
  const errors = {};

  if (!values.name) {
    errors.name = "Name must not be empty.";
  } else if (values.name.length > 15) {
    errors.name = "Must be 15 characters or less.";
  }

  if (!values.email) {
    errors.email = "Email must not be empty.";
  } else if (!emailRule.test(values.email)) {
    errors.email = "Please enter a valid email.";
  }

  return errors;
};

const SimpleForm = () => {
  const formik = useFormik({
    initialValues: {
      name: "",
      email: ""
    },
    validate,
    onSubmit: (values, { resetForm }) => {
      console.log(values);
      resetForm();
    }
  });

  const showNameError = formik.touched.name && formik.errors.name;
  const showEmailError = formik.touched.email && formik.errors.email;

  return (
    <form onSubmit={formik.handleSubmit}>
      <label htmlFor="name">Your Name</label>
      <input
        type="text"
        id="name"
        name="name"
        onChange={formik.handleChange}
        onBlur={formik.handleBlur}
        value={formik.values.name}
        className={showNameError ? "invalid" : ""}
      />
      {showNameError ? (
        <p className="error-text">{formik.errors.name}</p>
      ) : null}
      <label htmlFor="email">Your E-Mail</label>
      <input
        type="email"
        id="email"
        name="email"
        onChange={formik.handleChange}
        onBlur={formik.handleBlur}
        value={formik.values.email}
        className={showEmailError ? "invalid" : ""}
      />
      {showEmailError ? (
        <p className="error-text">{formik.errors.email}</p>
      ) : null}
      <button type="submit">Submit</button>
    </form>
  );
};

export default SimpleForm;

4. 改用 Yup 去驗證表單輸入值

雖然上個步驟的表單已經算完成了,不過我們來試試使用 Yup 吧!

將 validationSchema 和驗證規則寫在 useFormik 物件參數內即可。

import { useFormik } from "formik";
import * as Yup from "yup";

import "./styles.css";

const SimpleForm = () => {
  const formik = useFormik({
    initialValues: {
      name: "",
      email: ""
    },
    validationSchema: Yup.object({
      name: Yup.string()
        .max(15, "Must be 15 characters or less.")
        .required("Name must not be empty."),
      email: Yup.string()
        .email("Please enter a valid email.")
        .required("Email must not be empty.")
    }),
    onSubmit: (values, { resetForm }) => {
      console.log(values);
      resetForm();
    }
  });

  const showNameError = formik.touched.name && formik.errors.name;
  const showEmailError = formik.touched.email && formik.errors.email;

  return (
    <form onSubmit={formik.handleSubmit}>
      <label htmlFor="name">Your Name</label>
      <input
        type="text"
        id="name"
        name="name"
        onChange={formik.handleChange}
        onBlur={formik.handleBlur}
        value={formik.values.name}
        className={showNameError ? "invalid" : ""}
      />
      {showNameError ? (
        <p className="error-text">{formik.errors.name}</p>
      ) : null}
      <label htmlFor="email">Your E-Mail</label>
      <input
        type="email"
        id="email"
        name="email"
        onChange={formik.handleChange}
        onBlur={formik.handleBlur}
        value={formik.values.email}
        className={showEmailError ? "invalid" : ""}
      />
      {showEmailError ? (
        <p className="error-text">{formik.errors.email}</p>
      ) : null}
      <button type="submit">Submit</button>
    </form>
  );
};

export default SimpleForm;

5. 使用 getFieldProps() 稍微簡化程式碼

目前已經完成了表單的驗證,不過會發現到每個輸入欄都有一樣的 onChange={formik.handleChange}onBlur={formik.handleBlur}等重複的程式碼,這時就可以使用 getFieldProps() 去減少這些程式碼。

// 原本的程式碼
<input
  type="text"
  id="name"
  name="name"
  onChange={formik.handleChange}
  onBlur={formik.handleBlur}
  value={formik.values.name}
  className={showNameError ? "invalid" : ""}
/>

<input
  type="email"
  id="email"
  name="email"
  onChange={formik.handleChange}
  onBlur={formik.handleBlur}
  value={formik.values.email}
  className={showEmailError ? "invalid" : ""}
/>

// 加入 getFieldProps()
<input
  type="text"
  id="name"
  name="name"
  {...formik.getFieldProps("name")}
  className={showNameError ? "invalid" : ""}
/>
<input
  type="email"
  id="email"
  name="email"
  {...formik.getFieldProps("email")}
  className={showEmailError ? "invalid" : ""}
/>

6. 使用 Formik 元件取代 useFormik hook

在前面的實作,我們是透過 useFormik 去了解怎麼將表單加入驗證功能,而現在我們要進行改寫,會用到一個有使用到 React Context 的 Formik 的元件去取代 useFormik hook。

官網提供的關於 Formik 元件的程式碼:

 import React from 'react';
 import { useFormik } from 'formik';
 
 // Create empty context
 const FormikContext = React.createContext({});
 
 // Place all of what’s returned by useFormik into context
 export const Formik = ({ children, ...props }) => {
   const formikStateAndHelpers = useFormik(props);
   return (
     <FormikContext.Provider value={formikStateAndHelpers}>
       {typeof children === 'function'
         ? children(formikStateAndHelpers)
         : children}
     </FormikContext.Provider>
   );
 };

以下是改寫的結果,Formik 元件接受了一個函式做為它的 children:

import { Formik } from "formik";
import * as Yup from "yup";

import "./styles.css";

const SimpleForm = () => {
  return (
    <Formik
      initialValues={{ name: "", email: "" }}
      validationSchema={Yup.object({
        name: Yup.string()
          .max(15, "Must be 15 characters or less.")
          .required("Name must not be empty."),
        email: Yup.string()
          .email("Please enter a valid email.")
          .required("Email must not be empty.")
      })}
      onSubmit={(values, { resetForm }) => {
        console.log(values);
        resetForm();
      }}
    >
      {(formik) => (
        <form onSubmit={formik.handleSubmit}>
          <label htmlFor="name">Your Name</label>
          <input
            type="text"
            id="name"
            name="name"
            {...formik.getFieldProps("name")}
            className={
              formik.touched.name && formik.errors.name ? "invalid" : ""
            }
          />
          {formik.touched.name && formik.errors.name ? (
            <p className="error-text">{formik.errors.name}</p>
          ) : null}
          <label htmlFor="email">Your E-Mail</label>
          <input
            type="email"
            id="email"
            name="email"
            {...formik.getFieldProps("email")}
            className={
              formik.touched.email && formik.errors.email ? "invalid" : ""
            }
          />
          {formik.touched.email && formik.errors.email ? (
            <p className="error-text">{formik.errors.email}</p>
          ) : null}
          <button type="submit">Submit</button>
        </form>
      )}
    </Formik>
  );
};

export default SimpleForm;

注意把 useFormik 的參數物件移到 Formik 元件內時要改成 JSX 語法

7. 加入 Field、Form、ErrorMessage 元件

最後一個步驟,加入一些 formik 提供的元件,讓程式碼再次變更精簡!

import { Formik, Field, Form, ErrorMessage } from "formik";
import * as Yup from "yup";

import "./styles.css";

const SimpleForm = () => {
  return (
    <Formik
      initialValues={{ name: "", email: "" }}
      validationSchema={Yup.object({
        name: Yup.string()
          .max(15, "Must be 15 characters or less.")
          .required("Name must not be empty."),
        email: Yup.string()
          .email("Please enter a valid email.")
          .required("Email must not be empty.")
      })}
      onSubmit={(values, { resetForm }) => {
        console.log(values);
        resetForm();
      }}
    >
      {(formik) => (
        <Form onSubmit={formik.handleSubmit}>
          <label htmlFor="name">Your Name</label>
          <Field
            name="name"
            render={({ field, meta }) => (
              <input
                type="text" {...field}
                className={meta.error ? "invalid" : ""}
              />
            )}
          />
          <ErrorMessage name="name">
            {(err) => <p className="error-text">{err}</p>}
          </ErrorMessage>
          <label htmlFor="email">Your E-Mail</label>
          <Field
            name="email"
            render={({ field, meta }) => (
              <input
                type="text" {...field}
                className={meta.error ? "invalid" : ""}
              />
            )}
          />
          <ErrorMessage name="email">
            {(err) => <p className="error-text">{err}</p>}
          </ErrorMessage>
          <button type="submit">Submit</button>
        </Form>
      )}
    </Formik>
  );
};

export default SimpleForm;

這樣就完成了所有的實作,底下附上放在 codesandbox 上的程式碼。

範例程式碼(codesandbox)


上一篇
Day12-React 表單驗證篇-使用 Custom hook 進行表單的驗證
下一篇
Day14-Redux 篇-介紹 Redux
系列文
用30天更加認識 React.js 這個好朋友32

尚未有邦友留言

立即登入留言