Next.js (2) การใช้งาน useState ร่วมกับ fetch

Web Development 20 เมษายน พ.ศ. 2567 373
Home / Articles / 802

ในบทความนี้ผมจะมาแนะนำ การดึงข้อมูลจาก Backend โดยใช้ Fetch ซึ่งจะมีการใช้ useState ในการติดตามสถานะ ว่าดึงข้อมูลออกมาแล้วหรือยัง เหตุผลที่ต้องใช้ useState ก็คือ ฝั่ง Client-Side นั่นหากเราดึงข้อมูล โดยไม่ติดตามสถานะ จะทำให้ React ดึงข้อมูลอยู่ตลอดเวลา ไม่รู้ว่าควรหยุดหรือไม่ ทำให้เกิดสิ่งที่เรียกว่า "Waterfall Fetch" ซึ่งคือการดึงข้อมูลไม่จบไม่สิ้นนั่นเอง สุดท้ายจะทำให้ Web Brower เกิดอาการค้างขึ้นมาได้

ตัวอย่างที่ผมจะทำก็คือ การดึงข้อมูล JSON ดังนี้มาจาก Backend และนำมาแสดงในหน้า /about

{
    "persons": [
        {
            "name": "Sunny Jirakit",
            "major": "Computer Science",
            "age": 22
        }
    ]
}

ซึ่งผมจะแบ่งการทำงานเป็นสองฝั่ง ได้แก่ ฝั่ง Server Side ผมจะมีฟังก์ชั่นที่ชื่อ getData ใช้ Fetch และ Return ออกมาเป็น JSON และฝั่งของ Client ซึ่งผมจะเขียนลงไปในไฟล์ page.tsx เดิมของเราเลย

ก่อนแรกผมจะทำการสร้างไฟล์สำหรับ Server Side Function ขึ้นมาก่อน โดยจะตั้งชื่อว่า server.tsx อยู่ใน /src/app/about โดยจะมีการสร้างฟังก์ชั่น เพื่อใช้ในการ Fetch ดังนี้  โดยเราจะใส่ "use server" ลงไปด้วย เพื่อให้ Next อยู่ว่าเราต้องการให้สิ่งที่อยู่ในไฟล์นี้ประมวลผลจาก Server

/src/app/about/server.tsx
"use server"
export async function getData() {
    const data = await fetch('http://localhost:3002/getData')
    if (!data.ok) {
        throw new Error('Failed to fetch data')
    }
    return data.json()
}

useState และ useEffects

กลับมาที่ไฟล์ /src/app/about/page.tsx เราจะมีการ Import useState และ useEffects เข้ามา ใช้ในการเก็บค่าสถานะ

โดยผมจะมี สถานะสองสถานะ ได้แก่

isLoad สำหรับเช็คว่าขณะนี้กำลังโหลดข้อมูลอยู่หรือไม่ โดยจะมีค่าเริ่มต้นเป็น true และเมื่อดึงข้อมูลเสร็จแล้ว จึงจะเปลี่ยนเป็น false

data สำหรับเก็บข้อมูลที่เราดึงมาจาก backend ซึ่งจะสามารถเรียกใช้ภายใน funtion ได้ โดยมีค่าเริ่มต้นเป็น array ว่าง []

เวลาเราจะสร้างสถานะใหม่ เราก็จะสร้าง array ขึ้นมา โดยมีสองตัวแปรอยู่ด้านใน ตัวแรก "isLoad" เป็นตัวเก็บ Boolean ตัวที่สอง "setLoad" เป็นฟังก์ชั่นที่ใช้เปลี่ยนค่าของ isLoad จากนั้นเราจะใช้ = useState(true) หมายถึง ค่าเริ่มต้นของ isLoad จะเป็น true นั่นเอง

const [isLoad, setLoad] = useState(true)

เวลาเราจะกำหนดค่าใหม่ให้กับ isLoad เราก็จะเรียกใช้ setLoad ดังนี้

setLoad(false)

เราก็จะสามารถสร้างตัวติดตามสถานะ ได้ และสามารถเปลี่ยนค่าของสถานะนั้นๆ หลาย ๆ คนอาจจะใช้ useState ในการนับ 12345... แบบนี้ก็ได้ เราสามารถนำไปประยุกต์ใช้ได้หลายรูปแบบ

กลับไปที่เรื่องของเรา ผมจะ import ฟังก์ชั่น getData() จากไฟล์ server.tsx มาอยู่ใน page.tsx และกำหนด useState ที่ผมต้องการใช้ขึ้นมา

/src/app/about/page.tsx
"use client"
import { useEffect, useState } from "react"
import { getData } from "./server"

export default function About() {
    const [isLoad, setLoad] = useState(true)
    const [data,setData] = useState([])

    useEffect(() => {
        getData().then((persons_data) => {
            setData(persons_data)
            setLoad(false)
        })
    }, [])

    if(isLoad) {
        return ("Loading...")
    } else {
        return (
          <>
            <h1>About Page</h1>
          </>
        )
    }
}

จะเห็นได้ว่าผมมีการใช้ useEffects() ด้วย ซึ่ง useEffects ก็คือสิ่งที่หากเราใส่ ฟังก์ชั่น อะไรบางอย่างเข้าไป มันจะถูกเรียกใช้เมื่อหน้าเว็บถูกรันโดยอัตโนมัติ โดยที่ผู้ใช้ไม่ต้องมีการเรียกใช้เอง (คล้ายกับ ngInit ของ Angular)

เนื่องจากผมต้องการให้ เมื่อเราเปิดหน้าเว็บขึ้นมาฟังก์ชั่น getData() จะถูกเรียกใช้ทันที จากนั้นเอาค่าที่  return ออกมาจาก getData เก็บไว้ในตัวแปร persons_data และเราจะใช้ setData, setLoad ที่เราสร้างขึ้นมา ใส่ค่าใหม่เข้าไปใน useState

แต่ตอนนี้เรายังไม่มีส่วนที่นำ data จาก useState มาแสดง ผมก็เลยจะสร้าง ฟังก์ชั่นใหม่ชื่อ ViewPersonalData ที่จะรับค่า data เข้ามาในฟังก์ชั่น และใช้ map() ในการ Loop ข้อมูลออกมาแสดง โดยจะมีการกำหนด type ของข้อมูลด้วย หน้าตาของฟังก์ชั่น ก็จะเป็นประมาณนี้

/src/app/about/page.tsx
type Person = {
    name: string,
    major:string,
    age:number
}
export function ViewPersonalData({data}:any) {
    return (
        data.persons.map((row:Person) => (
          <div key={row.name}>
            <h4>My name is {row.name}</h4>
            <p>I am {row.age} year old.</p>
            <p>I am currently studying major of {row.major}.</p>
          </div>
        ))
    )
}

จากนั้นเราจะเรียก ViewPersonalData พร้อมใส่ Attribute data และข้อมูล data จาก useState ลงไปด้วย

/src/app/about/page.tsx
"use client"
import { useEffect, useState } from "react"
import { getData } from "./server"

type Person = {
    name: string,
    major:string,
    age:number
}
export function ViewPersonalData({data}:any) {
    return (
        data.persons.map((row:Person) => (
          <div key={row.name}>
            <h4>My name is {row.name}</h4>
            <p>I am {row.age} year old.</p>
            <p>I am currently studying major of {row.major}.</p>
          </div>
        ))
    )
}

export default function About() {
    const [isLoad, setLoad] = useState(true)
    const [data,setData] = useState([])

    useEffect(() => {
        getData().then((persons_data) => {
            setData(persons_data)
            setLoad(false)
        })
    }, [])

    if(isLoad) {
        return ("Loading...")
    } else {
        return (
          <>
            <h1>About Page</h1>
            <ViewPersonalData data={data} />
          </>
        )
    }
}

ทีนี้เว็บไซต์ของเราก็จะนำข้อมูลออกมาแสดงเป็นที่เรียบร้อย

About Page

My name is Sunny Jirakit

I am 22 year old.

I am currently studying major of Computer Science.

อ้างอิง:

nextjs.org/docs

Profile Picture.
  • Name (Pen name): Sunny Jirakit (Sunny420x)
  • Study: Bachelor Degree of Computer Science from Chiang Mai Rajabhat University
  • Personality: Architect (INTJ-T)
  • Experience: JavaScript,  Angular.js, React.js, Next.js  Express.js, Unity C#, Socket.io