Data Fetching ใน React: คิดให้ครบ Loading, Error, Cache และ Retry
การดึงข้อมูลใน React ไม่ใช่แค่เรียก API แล้วอัปเดต state แต่เป็นการออกแบบประสบการณ์ผู้ใช้ผ่านการจัดการ loading, error, cache และ retry ให้เหมาะกับแต่ละสถานการณ์ หากคิดไม่ครบ UI จะกระพริบ สับสน และดูไม่น่าเชื่อถือทันที

Data Fetching ใน React: คิดให้ครบ Loading, Error, Cache และ Retry
หลายคนเริ่มทำ Data Fetching ใน React ด้วยภาพจำง่าย ๆ ว่าแค่ “ยิง API แล้ว setState” ก็จบ แต่เมื่อแอปเริ่มมีผู้ใช้จริงและหน้าจอเริ่มซับซ้อนขึ้น วิธีคิดแบบนั้นมักไม่พออีกต่อไป
เบื้องหลังการดึงข้อมูลที่ดี มักมีองค์ประกอบสำคัญอย่างน้อย 4 เรื่องเสมอ คือ loading, error, cache และ retry หากขาดข้อใดข้อหนึ่ง UI จะเริ่มแสดงอาการแปลก เช่น กระพริบโดยไม่จำเป็น รีเฟรชแล้วหน้าหายทั้งจอ หรือแสดงข้อผิดพลาดที่ไม่ช่วยให้ผู้ใช้ไปต่อได้
Data Fetching ที่ดีจึงไม่ใช่แค่เรื่องของโค้ด แต่เป็นการตัดสินใจด้าน UX ว่าในแต่ละสถานการณ์ เราอยากให้ผู้ใช้รู้สึกอย่างไรและไปต่อได้อย่างไร
Loading ไม่ได้มีแค่สถานะเดียว
คำว่า loading มักถูกมองว่าเป็นสถานะเดียว คือรอข้อมูลแล้วแสดงตัวหมุน แต่ในความเป็นจริง loading มีหลายรูปแบบ และแต่ละแบบควรแสดงผลต่างกัน
ตัวอย่างที่เห็นชัดคือ
- โหลดครั้งแรกของหน้า: อาจเหมาะกับการแสดง skeleton หรือ placeholder เต็มพื้นที่
- กำลัง refetch ข้อมูลเดิม: ควรคงข้อมูลเก่าไว้ก่อน แล้วแสดงเพียง spinner เล็ก ๆ หรือสัญญาณบางอย่างว่ากำลังอัปเดต
ประเด็นสำคัญคือ ผู้ใช้ไม่ควรถูกล้างหน้าจอทุกครั้งที่มีการดึงข้อมูลใหม่ เพราะจะทำให้ประสบการณ์ใช้งานสะดุดและดูไม่นิ่ง
นี่คือเหตุผลที่การแยกระหว่าง isLoading และ isFetching มีความหมายมาก
isLoadingมักหมายถึงยังไม่มีข้อมูลเริ่มต้นให้แสดงisFetchingหมายถึงกำลังดึงข้อมูลเพิ่มเติมหรือดึงซ้ำ แม้อาจยังมีข้อมูลเดิมอยู่แล้ว
เมื่อแยกสองสถานะนี้ชัดเจน UI จะดูเป็นธรรมชาติขึ้น และลดอาการกระพริบที่ผู้ใช้มักรู้สึกว่าแอป “ไม่เสถียร”
Error ที่ดีต้องบอกให้ผู้ใช้ไปต่อได้
ข้อผิดพลาดไม่ได้มีหน้าตาเดียวกันทั้งหมด การแสดงข้อความว่า “เกิดข้อผิดพลาด” แบบครอบจักรวาลอาจง่ายสำหรับนักพัฒนา แต่เป็นการผลักภาระให้ผู้ใช้ต้องเดาเองว่าเกิดอะไรขึ้น
ก่อนจัดการ error ควรถามก่อนว่าเป็น error ประเภทใด เช่น
- อินเทอร์เน็ตหลุด
- Server ตอบกลับ 500
- ผู้ใช้ไม่มีสิทธิ์เข้าถึงข้อมูล
- ไม่พบข้อมูลที่ต้องการ
- ข้อมูลที่ส่งมาไม่ผ่าน validation
แต่ละกรณีควรมีทางตอบสนองต่างกัน เช่น
- ถ้าเป็นปัญหาเครือข่าย อาจเปิดทางให้ retry ได้
- ถ้าเป็น
401หรือ403อาจควรพาผู้ใช้ไป login หรือแจ้งเรื่องสิทธิ์ - ถ้าไม่พบข้อมูล อาจเหมาะกับ empty state มากกว่าข้อความ error รุนแรง
- ถ้าเป็น validation error การ retry ซ้ำอัตโนมัติย่อมไม่ช่วยอะไร
React component ที่ดีจึงไม่ควรแค่รู้ว่า “พัง” แต่ควรรู้ด้วยว่า พังแบบไหน และผู้ใช้ควรทำอะไรต่อ
Cache คือเหตุผลที่แอปรู้สึกเร็ว
แอปที่ดีไม่จำเป็นต้องรอข้อมูลใหม่จาก API ทุกครั้งก่อนจะแสดงผลเสมอไป โดยเฉพาะข้อมูลที่เพิ่งเคยโหลดมาแล้วไม่นาน
ตัวอย่างเช่น หากผู้ใช้เปิดหน้า user/1 แล้วกลับมาเปิดอีกครั้ง การรอ API แบบเต็มรอบทุกครั้งทำให้แอปรู้สึกช้าโดยไม่จำเป็น ทางเลือกที่ดีกว่าคือ
- แสดงข้อมูลเก่าจาก cache ก่อน
- จากนั้นค่อย refetch อยู่เบื้องหลัง
- เมื่อข้อมูลใหม่มาแล้วค่อยอัปเดตหน้าจอ
แนวคิดนี้เรียกว่า stale-while-revalidate ซึ่งยอมให้ข้อมูล “เก่าได้ชั่วคราว” แต่ไม่ปล่อยให้ UI “ว่างเปล่าโดยไม่จำเป็น”
นี่คือแก่นสำคัญของประสบการณ์ใช้งานที่ดี เพราะในหลายกรณี ผู้ใช้รับได้กับข้อมูลที่ช้ากว่าปัจจุบันเล็กน้อย แต่ไม่ชอบหน้าจอโล่งหรือการรอซ้ำไปซ้ำมา
Server State ไม่ใช่ Global State ธรรมดา
จุดที่พลาดกันบ่อยคือการมองว่า response จาก API เป็นเพียง state ก้อนหนึ่งที่เก็บไว้ตรงไหนก็ได้ เช่นใน Redux แล้วถือว่าจบ
แต่ความจริงแล้ว server state มีเจ้าของอยู่ที่ server ไม่ได้อยู่กับฝั่ง client โดยสมบูรณ์ ข้อมูลแบบนี้มีคุณสมบัติสำคัญคือ
- หมดอายุได้
- ถูกผู้ใช้อื่นแก้ไขได้
- ต้องโหลดซ้ำได้
- ต้องมีวิธี sync กับข้อมูลจริง
ดังนั้นการเอาทุก response ไปเก็บแบบถาวรใน global state มักสร้างภาระตามมา เช่น ต้องจัดการอายุข้อมูลเอง ต้องรู้ว่าข้อมูลไหน stale แล้ว หรือควร invalidate ตอนไหน
ถ้าโจทย์หลักคือการจัดการ server state เครื่องมืออย่าง TanStack Query หรือ SWR มักตอบโจทย์กว่า เพราะออกแบบมาเพื่อดูแลเรื่อง cache, refetch, invalidation และ background update โดยตรง
Retry ต้องมีหลัก ไม่ใช่กดซ้ำแบบสุ่ม
retry เป็นอีกเรื่องที่ดูง่าย แต่หากทำไม่ดีอาจก่อปัญหาได้มากกว่าที่คิด
หลักพื้นฐานคือ ไม่ใช่ทุก request ที่ควร retry แบบเดียวกัน เช่น
GETมัก retry ได้ง่ายกว่า เพราะโดยทั่วไปไม่สร้าง side effectPOSTต้องระวังมากกว่า เพราะอาจสร้าง order ซ้ำ ส่งข้อความซ้ำ หรือแม้แต่ตัดเงินซ้ำ
หากจำเป็นต้อง retry การทำ mutation ควรคิดเรื่อง idempotency ให้รอบคอบ เช่น
- ใช้ idempotency key
- ออกแบบ backend ให้รับการส่งซ้ำได้อย่างปลอดภัย
- หรือให้ผู้ใช้ยืนยันการทำรายการอย่างชัดเจนก่อนลองใหม่
retry ที่ดีควรมี backoff เช่น
- รอ 1 วินาที
- แล้วรอ 2 วินาที
- แล้วรอ 4 วินาที
วิธีนี้ช่วยไม่ให้ client ถล่ม server ซ้ำในช่วงที่ระบบกำลังมีปัญหา และยังเป็นมารยาทพื้นฐานต่อระบบปลายทางอีกด้วย
ที่สำคัญคือต้องรู้ด้วยว่าเมื่อไรควรหยุด retry เช่นกรณี
401 Unauthorized403 Forbidden- validation error
เพราะต่อให้ลองอีกสิบครั้ง ผลลัพธ์ก็ไม่เปลี่ยน การ retry ในกรณีนี้จึงมีแต่เพิ่มภาระและทำให้ผู้ใช้สับสน
วิธีคิด Data Fetching ใน React ให้ชัดขึ้น
หากต้องการให้ component อ่านง่ายและ debug ง่ายขึ้น ลองแยกแนวคิดออกเป็นส่วน ๆ ดังนี้
- data: ข้อมูลที่สามารถนำไปแสดงผลได้
- status: สถานะหลักของการดึงข้อมูล
- error: เหตุผลที่ทำให้การดึงข้อมูลล้มเหลว
- refetch: กลไกสำหรับดึงข้อมูลใหม่อีกครั้ง
- cache policy: กติกาว่าข้อมูลนี้เก่าได้แค่ไหน และควรดึงใหม่เมื่อไร
เมื่อแยกแบบนี้ได้ชัด การตัดสินใจในระดับ UI จะง่ายขึ้นมาก เพราะเราไม่เอาทุกอย่างไปกองรวมไว้ใน boolean เดียวอย่าง loading = true/false
แต่ละหน้าควรมีนโยบายไม่เหมือนกัน
อีกเรื่องที่สำคัญคือ ไม่มี config เดียวที่เหมาะกับทุก endpoint เพราะข้อมูลแต่ละประเภทมีความต้องการต่างกัน
ตัวอย่างเช่น
- หน้า dashboard อาจยอมรับข้อมูลยอดขายที่เก่าได้ 30 วินาที
- หน้า checkout หรือราคาสินค้า อาจต้องการข้อมูลที่สดมาก
- หน้า profile อาจ cache ได้นานกว่า
- จำนวน notification อาจต้อง refetch บ่อยกว่าอย่างชัดเจน
การออกแบบ cache policy และ refetch strategy จึงควรอิงตามลักษณะของข้อมูล ไม่ใช่ตั้งค่าแบบเดียวทั้งระบบแล้วหวังว่าจะเหมาะกับทุกกรณี
หลักคิดสำคัญที่ช่วยให้ UI นิ่งขึ้น
หากสรุปเป็นหลักง่าย ๆ สำหรับ Data Fetching ที่ดีใน React อาจมีดังนี้
- อย่าทำให้ loading กลายเป็นการกระพริบของหน้าจอ
- อย่าทำให้ error เป็นทางตันที่ผู้ใช้ไปต่อไม่ได้
- อย่าทำให้ cache กลายเป็นข้อมูลหลอกหรือข้อมูลค้างโดยไม่รู้ตัว
- อย่าทำให้ retry กลายเป็นการยิง API แบบไม่มีมารยาท
เมื่อเราคิดเรื่องเหล่านี้ครบถ้วน component จะมีพฤติกรรมที่นิ่งขึ้น ผู้ใช้เข้าใจสิ่งที่เกิดขึ้นมากขึ้น และทีมพัฒนาก็จะ debug ได้ง่ายกว่าเดิมมาก
สรุป
Data Fetching ใน React ไม่ใช่แค่เรื่องของการเรียก API แล้วอัปเดต state แต่คือการออกแบบประสบการณ์ใช้งานผ่านการจัดการ loading, error, cache และ retry อย่างมีเจตนา
ถ้าเราแยกสถานะเหล่านี้ชัด เข้าใจธรรมชาติของ server state และเลือกนโยบายที่เหมาะกับข้อมูลแต่ละประเภท UI จะดูเสถียรขึ้น ผู้ใช้สับสนน้อยลง และระบบทั้งฝั่งพัฒนาและฝั่งใช้งานจะทำงานได้ราบรื่นกว่ามาก
สุดท้ายแล้ว React มีหน้าที่ render สิ่งที่เราตัดสินใจไว้เท่านั้น หากเราคิดเรื่อง state ของการดึงข้อมูลได้ดีตั้งแต่ต้น ผลลัพธ์ที่ได้ก็จะไม่ใช่แค่โค้ดที่ทำงานได้ แต่เป็นประสบการณ์ใช้งานที่ดีจริง ๆ