ฟอร์ม

HTML element ประเภท ฟอร์ม (form) จะทำงานแตกต่างจาก DOM element ประเภทอื่นๆ ใน React เล็กน้อย เนื่องจากโดยปกติ element ประเภทฟอร์ม จะมีการเก็บ state ไว้เป็นของตัวเองอยู่แล้ว (ไม่เกี่ยวกับ state ของ React) ดังตัวอย่างด้านล่างที่เป็นแบบฟอร์ม HTML ทั่วไปที่รับข้อมูลเพียงหนึ่งอย่างคือ ชื่อ

<form>
  <label>
    Name:
    <input type="text" name="name" />
  </label>
  <input type="submit" value="Submit" />
</form>

ตัวอย่างฟอร์มด้านบนนี้จะมีความสามารถเหมือนกับ HTML ฟอร์มทั่วไป ที่จะเปลี่ยนบราวเซอร์ไปหน้าเพจใหม่เมื่อผู้ใช้งานกดปุ่ม submit หากคุณต้องการให้ฟอร์มของคุณมีการทำงานแบบนี้ใน React คุณไม่จำเป็นต้องทำอะไรเพิ่มจากนี้เลย แต่โดยส่วนใหญ่แล้ว การจัดการเกี่ยวกับฟอร์มจะทำได้ง่ายและสะดวกมากกว่า หากสามารถใช้ฟังก์ชัน JavaScript ที่สามารถเข้าถึงข้อมูลที่ผู้ใช้งานได้กรอกผ่านฟอร์มนั้น ๆ ในการควบคุมการส่งฟอร์ม วิธีมาตรฐานที่จะสามารถทำสิ่งที่กล่าวมานี้ได้ คือใช้เทคนิคที่เรียกว่า “คอนโทรลคอมโพเนนท์” (controlled components)

คอนโทรลคอมโพเนนท์

ในภาษา HTML, element ประเภทฟอร์ม ตัวอย่างเช่น <input>, <textarea>, และ <select> จะมีการจัดการเก็บ state ของตัวเองและอัพเดทเมื่อผู้ใช้งานกรอกข้อมูลเพิ่ม. สำหรับ React, state จะถูกเก็บไว้ในคอมโพเนนท์ และสามารถอัพเดทได้เพียงวิธีเดียว นั่นคือการเรียกฟังก์ชัน setState().

เราสามารถรวม 2 สิ่งนี้เข้าด้วยกันโดยใช้ state ของ React ในการเก็บข้อมูลอินพุทของผู้ใช้ หรือที่เรียกว่า “ความจริงเพียงหนึ่งเดียว” (single source of truth) จากนั้น คอมโพเนนท์ React ที่เรนเดอร์ฟอร์มนี้ ก็จะควบคุมสิ่งที่เกิดขึ้นกับฟอร์มนั้นเมื่อผู้ใช้งานกรอกข้อมูลเพิ่มเติมด้วย. Element ประเภทฟอร์มที่ข้อมูลอินพุทจากผู้ใช้ถูกควบคุมด้วย React นี้เรียกว่า “คอนโทรลคอมโพเนนท์” (controlled component)

ตัวอย่างเช่น หากเราต้องการให้ตัวอย่างฟอร์มที่ถูกกล่าวถึงก่อนหน้านี้ log ข้อมูลชื่อที่ผู้ใช้กรอกเข้ามา เมื่อกดปุ่ม submit เราสามารถเขียนฟอร์มเป็นคอนโทรลคอมโพเนนท์ได้ดังนี้

class NameForm extends React.Component {
  constructor(props) {
    super(props);
    this.state = {value: ''};
    this.handleChange = this.handleChange.bind(this);
    this.handleSubmit = this.handleSubmit.bind(this);
  }

  handleChange(event) {    this.setState({value: event.target.value});  }
  handleSubmit(event) {
    alert('A name was submitted: ' + this.state.value);
    event.preventDefault();
  }

  render() {
    return (
      <form onSubmit={this.handleSubmit}>
        <label>
          Name:
          <input type="text" value={this.state.value} onChange={this.handleChange} />        </label>
        <input type="submit" value="Submit" />
      </form>
    );
  }
}

ทดลองบน CodePen

เนื่องจากเราได้ใส่แอตทริบิวต์ value ไปให้กับ element ของฟอร์ม, ค่าที่จะถูกแสดงบนหน้าจอจะเป็นค่า this.state.value เสมอ ทำให้ state ของ React มีสถานะเป็น “ความจริงเพียงหนึ่งเดียว” (single source of truth) และฟังก์ชัน handleChange จะถูกรันทุกครั้งเมื่อมีอินพุทใหม่เข้ามา ทำให้ค่าที่ถูกแสดงบนหน้าจอจะอัพเดททุกครั้งที่ผู้ใช้พิมพ์

<<<<<<< HEAD เมื่อเราใช้คอนโทรลคอมโพเนนท์, ทุก ๆ ค่าของ state ที่จะมีการเปลี่ยนแปลง จะมีฟังก์ชันสำหรับควบคุมการเปลี่ยนแปลงควบคู่ไปด้วยเสมอ จึงทำให้การแก้ไขหรือว่าตรวจสอบอินพุทของผู้ใช้เป็นไปได้อย่างง่ายดาย ตัวอย่างเช่น หากเราต้องการให้ชื่อที่ผู้ใช้กรอกเข้ามาเป็นตัวอักษรพิมพ์ใหญ่เสมอ เราสามารถทำได้ด้วยการเขียนฟังก์ชัน handleChange ดังนี้:

handleChange(event) {
  this.setState({value: event.target.value.toUpperCase()});}

======= With a controlled component, the input’s value is always driven by the React state. While this means you have to type a bit more code, you can now pass the value to other UI elements too, or reset it from other event handlers.

fa5e6e7a988b4cb465601e4c3beece321edeb812

Tag ประเภท textarea

สำหรับภาษา HTML, element ประเภท <textarea> จะแสดงข้อความตัวอักษรตามค่าที่เป็นลูกของมัน ดังนี้

<textarea>
  Hello there, this is some text in a text area
</textarea>

ใน React, <textarea> จะแสดงข้อความจากแอตทริบิวต์ value แทน ดังนั้น ฟอร์มที่ใช้ <textarea> จะสามารถเขียนได้เหมือนกับการเขียนฟอร์มที่ใช้ input บรรทัดเดียวทั่วไป ดังนี้

class EssayForm extends React.Component {
  constructor(props) {
    super(props);
    this.state = {      value: 'Please write an essay about your favorite DOM element.'    };
    this.handleChange = this.handleChange.bind(this);
    this.handleSubmit = this.handleSubmit.bind(this);
  }

  handleChange(event) {    this.setState({value: event.target.value});  }
  handleSubmit(event) {
    alert('An essay was submitted: ' + this.state.value);
    event.preventDefault();
  }

  render() {
    return (
      <form onSubmit={this.handleSubmit}>
        <label>
          Essay:
          <textarea value={this.state.value} onChange={this.handleChange} />        </label>
        <input type="submit" value="Submit" />
      </form>
    );
  }
}

สังเกตว่า this.state.value จะถูกตั้งค่าเริ่มต้นไว้เพื่อที่ textarea จะได้เริ่มต้นโดยที่มีข้อความบางอย่างแสดงอยู่ตั้งแต่แรก

Tag ประเภท select

ในภาษา HTML, <select> จะแสดง drop-down ลิสต์ ตัวอย่างเช่น HTML ด้านล่างนี้จะแสดง drop-down ลิสต์สำหรับเลือกรสชาติ

<select>
  <option value="grapefruit">Grapefruit</option>
  <option value="lime">Lime</option>
  <option selected value="coconut">Coconut</option>
  <option value="mango">Mango</option>
</select>

สังเกตว่าตัวเลือก Coconut ได้ถูกเลือกไว้เป็นค่าตั้งต้นผ่านแอตทริบิวต์ selected. สำหรับ React, แทนที่เราจะใช้แอตทริบิวต์ selected ใส่ไว้ให้กับ element option ที่เราอยากจะเลือก, เราจะใช้แอตทริบิวต์ value ใส่ให้กับแท็ก select ซึ่งเป็น element แม่แทน ซึ่งวิธีนี้จะทำให้เราใช้งานคอนโทรลคอมโพเนนท์ได้สะดวกมากกว่า เนื่องจากเราจำเป็นต้องเก็บและอัพเดทค่าที่เราเลือกเพียงที่เดียว (แทนที่จะต้องเก็บค่า selected ตามจำนวน option) ดังตัวอย่าง:

class FlavorForm extends React.Component {
  constructor(props) {
    super(props);
    this.state = {value: 'coconut'};
    this.handleChange = this.handleChange.bind(this);
    this.handleSubmit = this.handleSubmit.bind(this);
  }

  handleChange(event) {    this.setState({value: event.target.value});  }
  handleSubmit(event) {
    alert('Your favorite flavor is: ' + this.state.value);
    event.preventDefault();
  }

  render() {
    return (
      <form onSubmit={this.handleSubmit}>
        <label>
          Pick your favorite flavor:
          <select value={this.state.value} onChange={this.handleChange}>            <option value="grapefruit">Grapefruit</option>
            <option value="lime">Lime</option>
            <option value="coconut">Coconut</option>
            <option value="mango">Mango</option>
          </select>
        </label>
        <input type="submit" value="Submit" />
      </form>
    );
  }
}

ทดลองบน CodePen

โดยภาพรวม <input type="text">, <textarea> และ <select> จะทำงานเหมือนกันทั้งหมด นั่นคือ ทุก element จะรับค่าแอตทริบิวต์ value ไปเพื่อที่เราสามารถทำให้เป็นคอนโทรลคอมโพเนนท์ได้

หมายเหตุ

เราสามารถใส่ค่าแอตทริบิวต์ value เป็นอาร์เรย์ได้ โดยจะทำให้เราสามารถเลือกตัวเลือกหลาย ๆ ตัวเลือกใน select ได้พร้อม ๆ กัน:

<select multiple={true} value={['B', 'C']}>

Tag ประเภท file input

ในภาษา HTML, element ประเภท <input type="file"> ช่วยให้ผู้ใช้เลือกไฟล์จากเครื่องของผู้ใช้เพื่อทำการอัพโหลดหรือส่งมาจัดการด้วย JavaScript โดยใช้ File API

<input type="file" />

เนื่องจากค่า value ของอินพุท ประเภท file เป็นแบบที่ห้ามแก้ไข (read-only) ทำให้มันเป็นคอมโพเนนท์ที่ไม่สามารถควบคุมได้ (uncontrolled component) ใน React ซึ่งจะถูกพูดถึง หลังจากนี้

การจัดการอินพุทหลาย ๆ ค่า

เมื่อเราต้องการจัดการหลาย ๆ input, เราสามารถใส่แอตทริบิวต์ name ให้กับแต่ละ element และสร้างฟังก์ชันขึ้นมาจัดการแต่ละค่าตามแบบที่เราต้องการได้ โดยดูจากค่าของ event.target.name

ตัวอย่างเช่น:

class Reservation extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      isGoing: true,
      numberOfGuests: 2
    };

    this.handleInputChange = this.handleInputChange.bind(this);
  }

  handleInputChange(event) {
    const target = event.target;
    const value = target.type === 'checkbox' ? target.checked : target.value;
    const name = target.name;
    this.setState({
      [name]: value    });
  }

  render() {
    return (
      <form>
        <label>
          Is going:
          <input
            name="isGoing"            type="checkbox"
            checked={this.state.isGoing}
            onChange={this.handleInputChange} />
        </label>
        <br />
        <label>
          Number of guests:
          <input
            name="numberOfGuests"            type="number"
            value={this.state.numberOfGuests}
            onChange={this.handleInputChange} />
        </label>
      </form>
    );
  }
}

ทดลองบน CodePen

สังเกตว่าเราได้ใช้วิธีการเขียนแบบ ชื่อคุณสมบัติจากการคำนวณ (computed property name) ของ ES6 ในการอัพเดท key ของ state ที่ตรงกับค่า name ของอินพุท

this.setState({
  [name]: value});

ซึ่งจะเหมือนกับการเขียนแบบนี้ใน ES5

var partialState = {};
partialState[name] = value;this.setState(partialState);

นอกจากนี้ เนื่องจาก setState() จะทำการ รวมค่าของ state ที่ถูกอัพเดทเพียงบางส่วนเข้ากับ state ทั้งหมด โดยอัตโนมัติ เราจึงสามารถเรียกอัพเดท state เฉพาะแค่ค่าที่เปลี่ยนก็เพียงพอ

ค่า null ของอินพุทที่ถูกควบคุม

การระบุค่า value ที่เฉพาะเจาะจงให้กับ คอนโทรลคอมโพเนนท์ จะทำให้ผู้ใช้ไม่สามารถเปลี่ยนค่าอินพุทได้นอกจากว่าเราต้องการให้เปลี่ยน หากคุณพบว่าคุณได้ใส่ค่าที่เฉพาะเจาะจงให้กับ value แต่อินพุทกลับยังสามารถถูกแก้ไขได้ อาจเป็นเพราะว่าคุณได้ตั้งค่าของ value ให้เป็น undefined หรือ null โดยไม่ได้ตั้งใจ

โค้ดด้านล่างเป็นตัวอย่างของการที่ตอนแรกผู้ใช้ไม่สามารถแก้ไขอินพุทได้ในตอนแรก และเปลี่ยนเป็นสามารถแก้ไขได้เมื่อเวลาผ่านไปเล็กน้อย The following code demonstrates this. (The input is locked at first but becomes editable after a short delay.)

ReactDOM.render(<input value="hi" />, mountNode);

setTimeout(function() {
  ReactDOM.render(<input value={null} />, mountNode);
}, 1000);

ตัวเลือกอื่นนอกจากคอนโทรลคอมโพเนนท์

การใช้คอนโทรลคอมโพเนนท์บางครั้งอาจเป็นเรื่องที่ดูวุ่นวาย เนื่องจากเราต้องมาเขียนฟังก์ชันการจัดการ event สำหรับทุก ๆ วิธีที่จะทำให้ข้อมูลของเราเปลี่ยนแปลงได้ และยังต้องทำการเชื่อมทุก ๆ state ของ element input เข้ากับ state ของ React ซึ่งบางทีอาจเป็นเรื่องวุ่นวายมากเกินไปหากเราต้องมาเปลี่ยนแปลงโค้ดเก่าที่เคยมีอยู่แล้วมาเป็น React, หรือหากเราต้องการใช้ React คู่กับไลบรารีอื่น ๆ ที่ไม่ได้เขียนแบบ React. หากคุณตกอยู่ในสถานการณ์เช่นนี้ การใช้ คอมโพเนนท์ที่ไม่ถูกควบคุม (uncontrolled components) ซึ่งเป็นอีกวิธีหนึ่งสำหรับการทำแบบฟอร์มอินพุท อาจเหมาะสมกับคุณมากกว่า

วิธีการสำเร็จรูป

หากคุณต้องการวิธีการทำฟอร์มที่สำเร็จรูป ซึ่งรวมถึงการตรวจสอบอินพุท (validation), คอยนับและเช็คฟีลด์ที่ถูกเข้าถึง และควบคุมการส่งแบบฟอร์ม, Formik ถือเป็นหนึ่งในไลบรารีที่ได้รับความนิยมอย่างมากในการทำสิ่งนี้ อย่างไรก็ตาม หลาย ๆ ไลบรารีก็ถูกสร้างขึ้นมาจากหลักการของคอนโทรลคอมโพเนนท์และการจัดการ state ดังนั้นคุณไม่ควรมองข้ามการเรียนรู้สิ่งเหล่านี้ไป

Is this page useful?Edit this page