เขียนโปรแกรม CRUD ใช้งาน Cloud Firestore โดยใช้ React Hooks

Cloud Firestore คือบริการฐานข้อมูลประเภท NoSQL ที่จัดเก็บข้อมูลแบบ Online ซึ่งสามารถใช้งานได้ฟรีหากอยู่ในข้อจำกัดที่กำหนดไว้ บทความนี้เราจะลองมาเขียนโปรแกรม CRUD ด้วย React Hooks เพื่อใช้งานบริการฐานข้อมูลฟรีตัวนี้กันดูครับ

โดยบทความนี้จะไม่ขอพูดถึงพื้นฐานเกี่ยวกับ Cloud Firestore และฐานข้อมูลประเภท NoSQL นะครับ

ข้อจำกัดของการใช้งาน Firestore แบบฟรี

Firebase Account

เราสามารถใช้ Google Account เพื่อสมัคร Firebase Account และสร้าง Firebase Project ได้ที่ https://console.firebase.google.com

หลังจากสร้าง Firebase Project ก็สามารถดูค่า Configuration เพื่อใช้สำหรับการตั้งค่าในขั้นตอนต่อไป ได้จากการคลิ๊กตามลำดับที่ 1.และ2.

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

Path

+-src
|   |-firebase.js
|   |-Insert.jsx
|   |-Display.jsx
|   |-Delete.jsx
|   |-Update.jsx

พิมพ์คำสั่งที่ Terminal

# ติดตั้ง package firebase
npm install @firebase/app --save
npm install @firebase/firestore --save

Module สำหรับ Firebase Application

เพื่อความสะดวกในการเรียกใช้ Instance ของ Firebase Application จากไฟล์ Component แต่ละไฟล์ เราจึงแยกเขียน โค้ดในส่วนนี้ออกเป็น Module ซะเลย

ไฟล์ src/firebase.js

// เรียกใช้ module
import firebase from '@firebase/app'
import '@firebase/firestore'

// ค่า minimum configuration คือ `apiKey` และ `projectId`
const config = {
  apiKey: 'AIzaSyCSZnKyvz6a8aBIAzLSG...',
  projectId: 'tutor4dev-...'
}

export default firebase.apps[0] || firebase.initializeApp(config)

ใช้งาน Firebase Module

Component ที่ต้องการใช้งาน Instance ของ Firebase Application เราก็จะทำการ Import Module และจะใช้ตัว Instance ดังกล่าวในการสร้างตัวแปรเพื่อเข้าถึง Collection ชื่อ users ตามลำดับ

ในฐานข้อมูลแบบ NoSQL นั้นเราสามารถอ้างถึงชื่อ Collection ทั้งๆที่เราไม่เคยสร้าง Schema ของ Collection นี้มาก่อน

Code

// เรียกใช้ module
import firebaseApp from '../firebase'
// ประกาศตัวแปรเพื่ออ้างอิง user collection
const db = firebaseApp.firestore()
const userCollection = db.collection('users')

Insert Component

มาเริ่มต้นที่ Component ตัวแรกซึ่งทำหน้าที่ Insert ข้อมูล กันเลยครับ

ไฟล์ src/Insert.jsx

import React, { useState } from 'react'

/* โค้ดการเรียกใช้ firebaseApp */

function Insert() {
    // ประกาศตัวแปร state และ method สำหรับเปลี่ยนค่าตัวแปร
    const [firstName, setFirstName] = useState('')
    const [lastName, setLastName] = useState('')

    async function insertDocument() {
        // เว้นส่วนนี้ไว้ในขั้นตอนถัดไป
    }

    return <div>
        {/* ตัวแปร state จะถูกเปลี่ยนค่าเมื่อพิมพ์ข้อมูล และ trigger การ re-render */}
        <input type="text" value={ firstName } onChange={e => setFirstName(e.target.value)} />
        <input type="text" value={ lastName } onChange={e => setLastName(e.target.value)} />

        {/* เรียกใช้ method เมื่อ click ปุ่ม */}
        <button onClick={ insertDocument }>Save</button>
    </div>
}

***

ขั้นตอนต่อไปจะเป็นการเขียนโค้ดสำหรับ Insert ข้อมูลไปยัง Cloud Firestore ครับ โดยเราได้ใช้ State firstName และ lastName เป็นข้อมูลที่นำไป Insert

หลังจากการ Insert แล้ว Cloud Firestore จะคืน Reference Object ของ Document ที่เพิ่งจะถูก Insert สำเร็จ ซึ่งเราสามารถใช้ Object นี้เข้าถึงค่าที่เราต้องการได้ เช่น Document ID เป็นต้น

ไฟล์ src/Insert.jsx

async function insertDocument() {
    // insert และคืน document reference
    const documentRef = await userCollection.add({
        firstName,
        lastName
    })

    // ใช้ document reference เข้าถึงค่า document id
    alert(`new document has been inserted as ${ documentRef.id }`)
}

ข้อมูลถูกจัดเก็บใน Cloud Firestore แบบ Collection และ Document

Realtime Subscription

Cloud Firestore รองรับการผูก Subscription แบบ Realtime เช่นเดียวกับ Firebase Realtime Database ในขั้นตอนนี้เราต้องใช้งาน useEffect Hooks เพื่อจัดการกับ Lifecycle Method ของ React.js

ไฟล์ src/Display.jsx

// เรียกใช้ module
import React, { useState, useEffect } from 'react'

/* โค้ดการเรียกใช้ firebaseApp */

function Display() {
    // ประกาศตัวแปร state
    const [ users, setUsers ] = useState({})

    useEffect(() => {
        // subscription นี้จะเกิด callback กับทุกการเปลี่ยนแปลงของ collection users
        const unsubscribe = userCollection.onSnapshot(ss => {
            // ตัวแปร local
            const users = {}

            ss.forEach(document => {
                // manipulate ตัวแปร local
                users[document.id] = document.data()
            })

            // เปลี่ยนค่าตัวแปร state
            setUsers(users)
        })

        return () => {
            // ยกเลิก subsciption เมื่อ component ถูกถอดจาก dom
            unsubscribe()
        }
    }, [])

    return <div>
        {/* แสดงผล state users */}
        <pre>{ JSON.stringify(users) }</pre>
    </div>
}

users State

{
    "QJRxdiPlN4B9tuVUJd4T": {
        "firstName": "Steve",
        "lastName": "Jobs"
    },
    "saysTLfSQBFxCFh7NR7s": {
        "firstName": "Dan",
        "lastName": "Abramov"
    }
}

Delete Component

การจัดการกับ Document ของ Cloud Firestore นั้นเราจะต้องใช้เลข ID ของ Document ในการอ้างอิง ขั้นตอนนี้จึงจะเป็นการนำ users State มาแสดงคู่กับปุ่ม Delete โดยปุ่ม Delete จะส่งค่า Argument ซึ่งเป็นค่า ID ของ Document นั้นๆ

ไฟล์ src/Delete.jsx

function Delete() {
    /* โค้ด realtime subscription */

    async function deleteDocument(id) {
        // ประกาศตัวแปรเพื่ออ้างอิงไปยัง document ที่จะทำการลบ
        const documentRef = userCollection.doc(id)
        // ลบ document
        await documentRef.delete()

        alert(`document ${ id } has been deleted`)
    }

    return <div>
        { Object.keys(users).map(id => {
            return <button key={ id } onClick={ () => deleteDocument(id) }>Delete { id }</button>
        }) }
    </div>
}

***

Update Component

Update Component จะทำงานเป็น 2 ขั้นตอน ขั้นตอนแรกจะใช้ค่า Document ID อ่านค่า Document รายการที่ต้องการแก้ไขมาเก็บไว้กับตัวแปร State ซึ่งผูกกับ onChange Event ของ <input />

ขั้นตอนที่ 2 จะเป็นการใช้ค่าปัจจุบันของตัวแปร State บันทึกไปยัง Cloud Firestore

ไฟล์ src/Update.jsx

function Update() {
    async function readDocument(id) {
        // ประกาศตัวแปรเพื่ออ้างอิงไปยัง document ที่จะทำการลบ
        const documentRef = await userCollection.doc(id).get()

        // อ่านค่าของ document
        const { firstName, lastName } = documentRef.data()
        // เปลี่ยนแปลง state ตามค่าของ document
        setId(documentRef.id)
        setFirstName(firstName)
        setLastName(lastName)
    }

    async function updateDocument() {
        // update ค่าไปยัง cloud firestore โดยใช้ค่าจาก state
        const options = { merge: true }
        await userCollection.doc(id).set({ firstName, lastName }, options)
    }

    return <div>
        <p>ID: { id }</p>
    </div>
}

***

つづく