#การทำ Form Validation ด้วย React State (useState)

การตรวจสอบข้อมูลในฟอร์ม หรือ Form Validation เป็นส่วนสำคัญของ Web Application เช่น การตรวจสอบว่า Email ถูกต้องหรือไม่, Password มีความยาวเพียงพอหรือไม่ หรือผู้ใช้กรอกข้อมูลที่จำเป็นครบหรือยัง

ใน React เราสามารถทำ Validation ได้โดยไม่ต้องใช้ Library เพิ่มเติม เช่น Formik, React Hook Form, Yup หรือ Zod โดยใช้ useState เพื่อจัดการทั้งค่าของฟอร์มและข้อความ Error

บทความนี้จะแสดงแนวทางการทำ Validation ด้วย React State ตั้งแต่พื้นฐานจนถึงการนำไปใช้งานจริง


#1. แนวคิดพื้นฐาน

ฟอร์มแบบ Controlled Component ใน React จะเก็บค่าของ Input ไว้ใน State

ตัวอย่าง:

import { useState } from "react";

function App() {
  const [email, setEmail] = useState("");

  return (
    <input
      type="email"
      value={email}
      onChange={(e) => setEmail(e.target.value)}
    />
  );
}

export default App;

เมื่อผู้ใช้พิมพ์ข้อมูล ค่าใน <input> จะถูกเก็บไว้ใน State

แนวคิดหลักคือ

User Input
    ↓
onChange
    ↓
React State
    ↓
Validation
    ↓
Error Message

#2. สร้าง Form State

หากฟอร์มมีหลาย Field ควรเก็บข้อมูลไว้ใน Object เดียว

const [formData, setFormData] = useState({
  name: "",
  email: "",
  password: "",
});

และสร้าง State สำหรับ Error

const [errors, setErrors] = useState({});

โครงสร้างของ Error อาจเป็น

{
  name: "กรุณากรอกชื่อ",
  email: "รูปแบบ Email ไม่ถูกต้อง",
  password: "Password ต้องมีอย่างน้อย 8 ตัวอักษร"
}

#3. จัดการ Input ด้วย onChange

สามารถสร้าง Function เดียวสำหรับจัดการ Input ทุก Field

const handleChange = (e) => {
  const { name, value } = e.target;

  setFormData({
    ...formData,
    [name]: value,
  });
};

ตัวอย่าง Input

<input
  type="text"
  name="name"
  value={formData.name}
  onChange={handleChange}
/>

ข้อดีคือไม่จำเป็นต้องสร้าง handleNameChange, handleEmailChange หรือ handlePasswordChange แยกกัน


#4. สร้าง Validation Function

สามารถแยก Validation Logic ออกมาเป็น Function

const validate = () => {
  const newErrors = {};

  if (!formData.name.trim()) {
    newErrors.name = "กรุณากรอกชื่อ";
  }

  if (!formData.email.trim()) {
    newErrors.email = "กรุณากรอก Email";
  }

  if (!formData.password) {
    newErrors.password = "กรุณากรอก Password";
  } else if (formData.password.length < 8) {
    newErrors.password = "Password ต้องมีอย่างน้อย 8 ตัวอักษร";
  }

  setErrors(newErrors);

  return Object.keys(newErrors).length === 0;
};

หากไม่มี Error

Object.keys(newErrors).length === 0

จะได้ค่า true


#5. ตรวจสอบรูปแบบ Email

สามารถใช้ Regular Expression ตรวจสอบ Email

const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;

if (!formData.email.trim()) {
  newErrors.email = "กรุณากรอก Email";
} else if (!emailRegex.test(formData.email)) {
  newErrors.email = "รูปแบบ Email ไม่ถูกต้อง";
}

#6. ตรวจสอบข้อมูลตอน Submit

ตัวอย่าง

const handleSubmit = (e) => {
  e.preventDefault();

  if (!validate()) {
    return;
  }

  console.log("Form Data:", formData);
};

e.preventDefault() ใช้ป้องกัน Browser Reload Page เมื่อ Submit Form


#7. ตัวอย่าง Complete Form

import { useState } from "react";

function RegisterForm() {
  const [formData, setFormData] = useState({
    name: "",
    email: "",
    password: "",
  });

  const [errors, setErrors] = useState({});

  const handleChange = (e) => {
    const { name, value } = e.target;

    setFormData((prev) => ({
      ...prev,
      [name]: value,
    }));
  };

  const validate = () => {
    const newErrors = {};

    if (!formData.name.trim()) {
      newErrors.name = "กรุณากรอกชื่อ";
    }

    const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;

    if (!formData.email.trim()) {
      newErrors.email = "กรุณากรอก Email";
    } else if (!emailRegex.test(formData.email)) {
      newErrors.email = "รูปแบบ Email ไม่ถูกต้อง";
    }

    if (!formData.password) {
      newErrors.password = "กรุณากรอก Password";
    } else if (formData.password.length < 8) {
      newErrors.password = "Password ต้องมีอย่างน้อย 8 ตัวอักษร";
    }

    setErrors(newErrors);

    return Object.keys(newErrors).length === 0;
  };

  const handleSubmit = (e) => {
    e.preventDefault();

    if (!validate()) {
      return;
    }

    console.log("Submit:", formData);

    alert("สมัครสมาชิกสำเร็จ");
  };

  return (
    <form onSubmit={handleSubmit}>
      <div>
        <label>ชื่อ</label>

        <input
          type="text"
          name="name"
          value={formData.name}
          onChange={handleChange}
        />

        {errors.name && (
          <p style={{ color: "red" }}>
            {errors.name}
          </p>
        )}
      </div>

      <div>
        <label>Email</label>

        <input
          type="email"
          name="email"
          value={formData.email}
          onChange={handleChange}
        />

        {errors.email && (
          <p style={{ color: "red" }}>
            {errors.email}
          </p>
        )}
      </div>

      <div>
        <label>Password</label>

        <input
          type="password"
          name="password"
          value={formData.password}
          onChange={handleChange}
        />

        {errors.password && (
          <p style={{ color: "red" }}>
            {errors.password}
          </p>
        )}
      </div>

      <button type="submit">
        Register
      </button>
    </form>
  );
}

export default RegisterForm;

#8. Validation แบบ Real-time

บางระบบต้องการตรวจสอบข้อมูลทันทีขณะที่ผู้ใช้พิมพ์

สามารถสร้าง Function เช่น

const validateField = (name, value) => {
  let error = "";

  if (name === "email") {
    const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;

    if (!value.trim()) {
      error = "กรุณากรอก Email";
    } else if (!emailRegex.test(value)) {
      error = "Email ไม่ถูกต้อง";
    }
  }

  if (name === "password") {
    if (!value) {
      error = "กรุณากรอก Password";
    } else if (value.length < 8) {
      error = "Password ต้องมีอย่างน้อย 8 ตัวอักษร";
    }
  }

  setErrors((prev) => ({
    ...prev,
    [name]: error,
  }));
};

แล้วเรียกใน handleChange

const handleChange = (e) => {
  const { name, value } = e.target;

  setFormData((prev) => ({
    ...prev,
    [name]: value,
  }));

  validateField(name, value);
};

Flow จะกลายเป็น

Typing
  ↓
onChange
  ↓
Update State
  ↓
validateField()
  ↓
Update Error
  ↓
UI แสดงผลทันที

#9. Validation ตอน onBlur

อีกแนวทางคือ Validation เมื่อผู้ใช้ออกจาก Input

<input
  type="email"
  name="email"
  value={formData.email}
  onChange={handleChange}
  onBlur={(e) =>
    validateField(e.target.name, e.target.value)
  }
/>

แนวทางนี้ช่วยลดข้อความ Error ที่ปรากฏเร็วเกินไปในขณะที่ผู้ใช้ยังพิมพ์ไม่เสร็จ


#10. เพิ่ม Touched State

ในฟอร์มที่ต้องการ UX ที่ดีขึ้น สามารถเก็บว่า Field ใดเคยถูกใช้งานแล้ว

const [touched, setTouched] = useState({});

สร้าง Function

const handleBlur = (e) => {
  const { name } = e.target;

  setTouched((prev) => ({
    ...prev,
    [name]: true,
  }));
};

จากนั้นแสดง Error เฉพาะ Field ที่ผู้ใช้เคยแตะ

{touched.email && errors.email && (
  <p>{errors.email}</p>
)}

#11. ตัวอย่าง Validation หลายเงื่อนไข

#Username

if (!formData.username.trim()) {
  newErrors.username = "กรุณากรอก Username";
} else if (formData.username.length < 4) {
  newErrors.username = "Username ต้องมีอย่างน้อย 4 ตัวอักษร";
}

#Password

if (!formData.password) {
  newErrors.password = "กรุณากรอก Password";
} else if (formData.password.length < 8) {
  newErrors.password = "Password ต้องมีอย่างน้อย 8 ตัวอักษร";
}

#Confirm Password

if (!formData.confirmPassword) {
  newErrors.confirmPassword = "กรุณายืนยัน Password";
} else if (
  formData.confirmPassword !== formData.password
) {
  newErrors.confirmPassword = "Password ไม่ตรงกัน";
}

#Age

if (!formData.age) {
  newErrors.age = "กรุณากรอกอายุ";
} else if (Number(formData.age) < 18) {
  newErrors.age = "อายุต้องไม่น้อยกว่า 18 ปี";
}

#12. Checkbox Validation

ตัวอย่าง Terms and Conditions

State

const [formData, setFormData] = useState({
  email: "",
  acceptTerms: false,
});

จัดการ Checkbox

const handleChange = (e) => {
  const { name, value, type, checked } = e.target;

  setFormData((prev) => ({
    ...prev,
    [name]: type === "checkbox" ? checked : value,
  }));
};

Validation

if (!formData.acceptTerms) {
  newErrors.acceptTerms =
    "กรุณายอมรับเงื่อนไขการใช้งาน";
}

#13. Select Validation

<select
  name="role"
  value={formData.role}
  onChange={handleChange}
>
  <option value="">เลือก Role</option>
  <option value="student">Student</option>
  <option value="teacher">Teacher</option>
</select>

Validation

if (!formData.role) {
  newErrors.role = "กรุณาเลือก Role";
}

#14. Disable Submit Button

สามารถปิดปุ่ม Submit หาก Form ยังไม่สมบูรณ์

const isFormValid =
  formData.name.trim() &&
  formData.email.trim() &&
  formData.password.length >= 8;

จากนั้น

<button
  type="submit"
  disabled={!isFormValid}
>
  Register
</button>

#15. Validation ฝั่ง Client ไม่เพียงพอ

แม้ว่า React จะ Validate ข้อมูลแล้ว แต่ Server ยังต้อง Validate ซ้ำเสมอ

โครงสร้างที่เหมาะสมคือ

React Form
    ↓
Client Validation
    ↓
API Request
    ↓
Server Validation
    ↓
Database

เหตุผลคือ Client-side Validation สามารถถูกข้ามได้ เช่น ผู้ใช้เรียก API โดยตรงผ่าน Postman, curl หรือ Script

ดังนั้น Validation ฝั่ง Server ยังคงเป็นข้อบังคับด้านความถูกต้องและความปลอดภัย


#16. React State Validation เหมาะกับงานแบบไหน

React State Validation เหมาะกับ

  • Form ขนาดเล็ก
  • Login Form
  • Contact Form
  • Search Form
  • Registration Form ที่มี Field ไม่มาก
  • Workshop สำหรับเรียนรู้ React State
  • Prototype หรือ MVP

หาก Form มีความซับซ้อน เช่น

  • หลายสิบ Field
  • Nested Object
  • Dynamic Form
  • Field Array
  • Multi-step Form
  • Validation Rule จำนวนมาก

ควรพิจารณาใช้ Library เช่น

React Hook Form
      +
Zod / Yup

เพราะช่วยลด Boilerplate Code และจัดการ Validation Schema ได้เป็นระบบมากกว่า


#17. เปรียบเทียบแนวทาง

แนวทาง เหมาะกับ จุดเด่น
React State Form เล็ก เข้าใจง่าย ไม่ต้องติดตั้ง Library
React Hook Form Form กลาง-ใหญ่ Performance ดี Boilerplate ต่ำ
Formik Form ทั่วไป API เข้าใจง่าย มี ecosystem
Zod Schema Validation TypeScript Friendly
Yup Schema Validation ใช้งานร่วมกับ Form Library ได้ดี

#18. Best Practices

#1. แยก Validation Logic

แทนที่จะเขียนทุกอย่างใน handleSubmit

const validate = () => {
  // validation rules
};

ทำให้ Code อ่านง่ายและ Test ได้ง่ายกว่า

#2. ใช้ Functional State Update

setFormData((prev) => ({
  ...prev,
  [name]: value,
}));

ช่วยให้ปลอดภัยกว่าเมื่อต้อง Update State จากค่าก่อนหน้า

#3. ไม่แสดง Error เร็วเกินไป

สำหรับ UX ที่ดีควรใช้

onBlur
+
touched state

แทนการแสดง Error ทุกครั้งที่ผู้ใช้เริ่มพิมพ์

#4. Validate ทั้ง Client และ Server

Client Validation มีหน้าที่ช่วย UX

Server Validation มีหน้าที่รักษาความถูกต้องและความปลอดภัยของข้อมูล


#สรุป

การทำ Form Validation ด้วย React State สามารถใช้ useState จัดการข้อมูลหลัก 3 ส่วน

formData
errors
touched

กระบวนการหลักคือ

User Input
    ↓
handleChange
    ↓
formData State
    ↓
Validation
    ↓
errors State
    ↓
Display Error
    ↓
Submit

สำหรับฟอร์มขนาดเล็ก การใช้ React State เป็นวิธีที่ตรงไปตรงมาและช่วยให้เข้าใจกลไก Controlled Component ของ React ได้เป็นอย่างดี

แต่หากระบบมี Form จำนวนมากหรือ Validation ซับซ้อน ควรพิจารณาใช้ React Hook Form + Zod เพื่อให้ Code ดูแลรักษาง่ายและรองรับการขยายระบบได้ดีกว่า