จัดการกับ State ของ Form ด้วย React Hooks

การจัดการ State ของ Form ใน Class Component ของ React นั้นเป็นสิ่งที่ยุ่งยากอย่างมาก ยิ่งเมื่อเทียบกับคู่แข่ง เช่น Angular และ Vue เรียกได้ว่าเป็นจุดอ่อนของ React จุดนึงเลยทีเดียว ในบทความนี้จะเป็นการใช้ของเล่นใหม่ที่เรียกว่า React Hooks มาช่วยในการจัดการ State ของ Form ซึ่งเมื่อเทียบกับวิธีการเดิมแล้วเรียกได้ว่าสะดวกและง่ายกว่าเดิมมากครับ

ในตอนท้ายของบทความเราจะลอง Implement Custom Hooks อย่างง่ายเพื่อเป็นการ Reuse Logic ในการจัดการ State ของ Form ด้วยเช่นกันครับ

เตรียมไฟล์สำหรับเขียนโปรแกรม

Path

+-src
|   |-FormApplication.jsx
|   |-custom-hooks.js

React Functional Component

เรามาเริ่มต้นด้วยการเขียน Functional Component ตามตัวอย่างโค้ดด้านล่างครับ

ไฟล์ src/FormApplication.jsx

import React from 'react'

function FormApplication() {
    return <div>
        <input type="text" />
        <input type="text" />
    </div>
}

Functional Component และ State

เมื่อไหร่ก็ตามที่เราต้องการใช้งาน State ภายใน Funtional Component ของ React ก็ต้องนี่เลยครับ useState() ใช้งานง่ายตรงไปตรงมา

ไฟล์ src/FormApplication.jsx

import React, { useState } from 'react'

function FormApplication() {
    const [ firstName, setFirstName ] = useState('Steve')
    const [ lastName, setLastName ] = useState('Jobs')

    return <div>
        <input type="text" value={ firstName } />
        <input type="text" value={ lastName } />
    </div>
}

useState() คืนค่าในแบบ Array เนื่องจากลำดับของ Array เป็นสิ่งสำคัญโดยตำแหน่งที่ 0 จะเป็นตัว State ในขณะที่ตำแหน่งที่ 1 จะเป็น Method ที่ทำหน้าที่ในการ Mutate State นั้นๆ

จัดการกับ onChange Event

เมื่อเกิดการเปลี่ยนแปลงค่าใน <input /> จะเกิด onChange Event ขึ้น สิ่งที่เราต้องการคือนำ Method ที่จะทำการ Mutate State ของเรามา Binding กับ onChange props ของ <input /> ซึ่งหากมีการ Invoke Method ดังกล่าวก็จะเป็นการ Trigger ให้เกิดการ Re-render ใหม่ครับ

โดยเราสามารถ Binding Method จาก Hooks โดยตรงหรือกำหนด Nested Function เพื่อเรียกใช้งาน Method จาก Hooks อีกทอดก็ได้เช่นกัน

ไฟล์ src/FormApplication.jsx

function FormApplication() {
    const [ firstName, setFirstName ] = useState('Steve')
    const [ lastName, setLastName ] = useState('Jobs')

    function handleChangeLastName(e) {
        setLastName(e.target.value)
    }

    return <div>
        <input type="text" value={ firstName } onChange={ e => setFirstName(e.target.value) } />
        <input type="text" value={ lastName } onChange={ handleChangeLastName } />
    </div>
}

Custom Hooks

เพื่อเป็นการ Reuse ส่วนของการจัดการ onChange Event เราจึงใช้วิธีการกำหนด Custom Hooks อย่างง่าย ขึ้นมาสักตัว โดย Custom Hooks ก็คือ JavaScript Function นั่นเอง

เราจะตั้งชื่อ Custom Hooks ตัวนี้ว่า useFormInput ซึ่งจะรับ Argument เพื่อใช้เป็นค่าเริมต้นและ Custom Hooks เองก็สามารถจะเรียกใช้ Hooks ตัวอื่นๆ ได้เช่นกัน ในส่วนท้ายเจ้า useFormInput ก็จะต้องคืนค่า State และ Mutate Method ในแบบ Plain JavaScript Object ครับ

ไฟล์ custom-hooks.js

export default function useFormInput(initValue) {
    const [ value, setValue ] = useState(initValue)

    function handleChange(e) {
        setValue(e.target.value)
    }

    return {
        onChange: handleChange,
        value
    }
}

เราสามารถคืนค่าจาก Custom Hooks ในแบบ Array เหมือนกับกรณีของ useState() ได้เช่นกัน แต่เพื่อความสะดวกในการนำไปใช้งานในลำดับต่อไปเราจึงเลือกที่จะคืนค่าในแบบ Plain JavaScript Object ครับ

ใช้งาน useFormInput() Hooks

มาถึงขั้นตอนสุดท้ายแล้วนะครับ นั่นก็คือการนำ useFormInput() มาใช้นั่นเอง

ไฟล์ src/FormApplication.jsx

import useFormInput from './custom-hooks'

function FormApplication() {
    const { value, onChange } = useFormInput('Steve')
    const lastName = useFormInput('Jobs')

    return <div>
        <input type="text" onChange={ onChange } value={ value } />
        <input type="text" { ...lastName } />
    </div>
}

ในกรณีของ useFormInput() นั้นการคืนค่าในแบบ Plain JavaScript Object จะมีความสะดวกในการนำไป Binding มากกว่าเนื่องจากเราสามารถใช้ Spread Syntax ซึ่งทำให้โค้ดที่เขียนค่อนข้างจะกระชับกว่าพอสมควร

つづく