2 นาที

การจัดการสถานะ React: ทำให้เรียบง่ายในแอปที่สร้างอัตโนมัติ

การจัดการสถานะใน React ให้เรียบง่าย: แยกสถานะเซิร์ฟเวอร์ออกจากสถานะ UI ยึดกฎไม่กี่ข้อ และสังเกตสัญญาณเริ่มต้นของความซับซ้อน

การจัดการสถานะ React: ทำให้เรียบง่ายในแอปที่สร้างอัตโนมัติ

อะไรผิดพลาดกับ state ในแอป React จริง ๆ

State คือข้อมูลที่เปลี่ยนได้ขณะที่แอปกำลังทำงาน ซึ่งรวมทั้งสิ่งที่คุณเห็น (เช่น modal เปิดอยู่), สิ่งที่กำลังแก้ไข (ร่างฟอร์ม), และข้อมูลที่ดึงมา (รายการโปรเจกต์). ปัญหาคือสิ่งพวกนี้ทั้งหมดมักถูกเรียกว่ากันว่า "state" ทั้งที่พฤติกรรมต่างกันมาก.

แอปที่ยุ่งเหยิงมักพังด้วยวิธีเดียวกัน: หลายประเภท state ถูกผสมกันในที่เดียว คอมโพเนนต์อาจถือทั้งข้อมูลจากเซิร์ฟเวอร์, ธง UI, ร่างฟอร์ม, และค่าที่ได้มาจากการคำนวณ แล้วพยายามซิงก์พวกมันด้วย effects. ไม่ช้าก็เร็ว คุณจะตอบคำถามง่าย ๆ เช่น "ค่าตัวนี้มาจากไหน?" หรือ "อะไรอัปเดตมัน?" ไม่ได้โดยไม่ไล่หาผ่านไฟล์หลายไฟล์.

แอป React ที่สร้างโดย generator มักลื่นไหลไปสู่สภาพนี้เร็วกว่าเพราะยอมรับเวอร์ชันแรกที่ใช้งานได้ง่าย: เพิ่มหน้าจอใหม่, คัดลอก pattern, แปะบั๊กด้วย useEffect อีกอัน — แล้วคุณมีสองแหล่งความจริง. ถ้า generator หรือทีมเปลี่ยนทิศทางกลางคัน (local state ที่นี่, global store ที่นั่น) โค้ดจะสะสม pattern แทนที่จะสร้างบนฐานเดียว.

เป้าหมายคือ "ทำให้เรียบง่าย": ลดชนิดของ state และลดที่ต้องค้นหา เมื่อมีบ้านที่ชัดเจนสำหรับข้อมูลจากเซิร์ฟเวอร์และตำแหน่งที่ชัดเจนสำหรับ state เฉพาะ UI บั๊กจะเล็กลงและการเปลี่ยนแปลงไม่รู้สึกเสี่ยง.

"ทำให้เรียบง่าย" หมายถึงการยึดตามกฎไม่กี่ข้อ:

  • อย่า mirror ข้อมูลจากเซิร์ฟเวอร์เข้า client state เว้นแต่มีเหตุผลชัดเจน
  • อย่า promote state UI ท้องถิ่นไปเป็น global store แค่เพราะอาจมีประโยชน์ในอนาคต
  • อย่าให้ค่าที่ได้มาจากการคำนวณกลายเป็น state ของตัวเองเว้นแต่การคำนวณจะแพงจริง ๆ

ตัวอย่างปฏิบัติ: ถ้ารายการผู้ใช้มาจาก backend ให้ถือว่าเป็น server state และดึงเมื่อใช้งาน. ถ้า selectedUserId มีไว้เพื่อขับรายละเอียด ให้เก็บเป็น state UI ขนาดเล็กใกล้กับแผงรายละเอียด. การผสมทั้งสองคือจุดเริ่มต้นของความซับซ้อน.

สถานะเซิร์ฟเวอร์ vs สถานะไคลเอนต์ แบบเข้าใจง่าย

ปัญหาส่วนใหญ่เริ่มจากการสับสนอย่างเดียว: เอาข้อมูลเซิร์ฟเวอร์ไปคิดว่าเป็น UI state. แยกตั้งแต่ต้น แล้วการจัดการ state จะสงบแม้แอปจะเติบโต.

Server state เป็นของ backend: users, orders, tasks, permissions, prices, feature flags. มันอาจเปลี่ยนได้โดยที่แอปคุณทำอะไรไม่เกี่ยว (แท็บอื่นอัปเดต, admin แก้, งานรัน, ข้อมูลหมดอายุ). เพราะมันถูกแชร์และเปลี่ยนได้ คุณต้องมีการ fetch, caching, refetching, และการจัดการข้อผิดพลาด.

Client state คือสิ่งที่ UI ของคุณเท่านั้นสนใจตอนนี้: modal ตัวไหนเปิด, แท็บที่เลือก, toggle ตัวกรอง, ลำดับการจัดเรียง, sidebar ที่ย่อ, ร่างข้อความค้นหา. ถ้าคุณปิดแท็บแล้วหายไปก็ไม่เป็นไร.

การทดสอบง่าย ๆ: "ฉันรีเฟรชหน้าแล้วสร้างสิ่งนี้จากเซิร์ฟเวอร์ได้ไหม?"

  • ถ้าใช่ อาจเป็น server state.
  • ถ้าไม่ อาจเป็น client state.

ยังมี derived state ซึ่งช่วยให้คุณไม่ต้องสร้าง state เพิ่ม ค่าที่คำนวณได้จากค่าอื่น ๆ จึงไม่เก็บไว้ ตัวอย่างเช่น รายการที่ถูกกรอง ยอดรวม isFormValid และการแสดงผลว่า "ไม่มีข้อมูล" มักเป็น derived.

ตัวอย่าง: คุณดึงรายการโปรเจกต์ (server state). ตัวกรองที่เลือกและธงว่าไดอะล็อก "โปรเจกต์ใหม่" เปิดอยู่เป็น client state. รายการที่เห็นหลังกรองเป็น derived. ถ้าคุณเก็บรายการที่เห็นแยกต่างหาก มันจะล้าสมัยและคุณจะตามหาเหตุผลว่า "ทำไมมันเก่า?".

การแยกแบบนี้ช่วยเมื่อเครื่องมืออย่าง Koder.ai สร้างหน้าจอได้เร็ว: เก็บข้อมูล backend ในชั้นการดึงข้อมูลหนึ่งที่ชัดเจน เก็บตัวเลือก UI ใกล้คอมโพเนนต์ และหลีกเลี่ยงการเก็บค่าที่คำนวณได้.

ชุดกฎเรียบง่ายที่ป้องกันปัญหาได้ส่วนใหญ่

State เจ็บปวดเมื่อหนึ่งข้อมูลมีเจ้าของสองคน วิธีเร็วที่สุดในการรักษาความง่ายคือ ตัดสินว่าใครเป็นเจ้าของอะไรแล้วยึดตามนั้น.

  • ถือว่า API data เป็นสิ่งที่คุณ fetch และ cache แทนที่จะคอย maintain ด้วยมือใน component state
  • อย่าคัดลอกข้อมูลที่ดึงมาเข้าท้องถิ่น "เผื่อไว้" การคัดลอกสร้างความเบี่ยงเบน
  • เก็บ client state ใกล้ที่ใช้งาน ย้ายขึ้นเฉพาะเมื่อส่วนต่าง ๆ ของ tree ต้องใช้จริง ๆ
  • เก็บเฉพาะ ID และ flag เล็ก ๆ แทนอ็อบเจ็กต์ทั้งก้อน ดึงอ็อบเจ็กต์จาก cache เมื่อเราจะเรนเดอร์

ตัวอย่าง: คุณดึงรายการผู้ใช้และแสดงรายละเอียดเมื่อเลือก หนึ่งความผิดพลาดคือเก็บอ็อบเจ็กต์ผู้ใช้ที่เลือกทั้งอันใน state. ให้เก็บ selectedUserId แทน. รายละเอียดจะมองหาผู้ใช้จาก cache ดังนั้นการ refetch จะอัปเดต UI โดยไม่ต้องซิงก์เพิ่ม.

ในแอป React ที่สร้างโดย generator มักเห็น state ที่เครื่องมือสร้างมาดู "ช่วยเหลือ" โดยซ้ำกับข้อมูลเซิร์ฟเวอร์: เมื่อเห็นโค้ดที่ทำ fetch -> setState -> edit -> refetch หยุดก่อน. นั่นมักเป็นสัญญาณว่าคุณกำลังสร้างฐานข้อมูลที่สองในเบราว์เซอร์.

จะจัดการ server state โดยไม่ต้องคิดมากอย่างไร

Server state คือข้อมูลที่อยู่บน backend: รายการ, หน้า detail, ผลลัพธ์การค้นหา, permissions, counts. วิธีน่าเบื่อคือเลือกเครื่องมือหนึ่งตัวและยึดตามมัน สำหรับแอป React หลายตัว TanStack Query มักพอเพียง.

เป้าหมายตรงไปตรงมา: คอมโพเนนต์ขอข้อมูล แสดง loading และ error และไม่ต้องสนใจจำนวนการเรียก fetch ใต้ผิวน้ำ นี่สำคัญในแอปที่สร้างเร็วเพราะความไม่สอดคล้องเล็ก ๆ ทวีคูณเมื่อตัวหน้าจอเพิ่มขึ้น.

ถือ query keys เหมือนระบบตั้งชื่อ อย่าเป็นของรอง: ให้คงที่เป็น array keys, ใส่แค่ input ที่เปลี่ยนผลลัพธ์ (filters, page, sort), และชอบรูปแบบที่คาดเดาได้ไม่กี่แบบมากกว่าของที่ทำครั้งเดียว หลายทีมยังใส่การสร้าง key ใน helper เล็ก ๆ เพื่อให้ทุกหน้าจอใช้กฎเดียวกัน.

สำหรับการเขียนข้อมูล (writes) ให้ใช้ mutations พร้อมการจัดการเมื่อสำเร็จที่ชัดเจน. mutation ควรตอบสองคำถาม: อะไรเปลี่ยน และ UI ควรทำอะไรต่อ?

ตัวอย่าง: คุณสร้างงานใหม่ เมื่อสำเร็จ ให้ invalidate query ของรายการงาน (ให้โหลดใหม่ครั้งหนึ่ง) หรืออัปเดตรายการใน cache แบบเจาะจง (เพิ่มงานใหม่เข้าไป). เลือกวิธีใดวิธีหนึ่งต่อฟีเจอร์และรักษาความสม่ำเสมอ.

ถ้ารู้สึกอยากเพิ่ม refetch ในหลายที่ "เผื่อให้ปลอดภัย" ให้เลือกการเคลื่อนไหวที่น่าเบื่อเดียวแทน:

  • Invalidated query key ที่เฉพาะเจาะจงที่หมดอายุ
  • อัปเดต cache สำหรับ query ที่เปลี่ยนแบบเจาะจง
  • นำทางไปหน้าที่เรียก fetch query ที่ถูกต้องอยู่แล้ว

รูปแบบ client state ที่ไม่โตเกินควบคุม

Share a real environment
Put your generated app on a custom domain for stakeholder reviews and real usage.

Client state คือสิ่งที่เบราว์เซอร์เป็นเจ้าของ: ธง sidebar เปิด, แถวที่เลือก, ข้อความตัวกรอง, ร่างก่อนบันทึก. เก็บมันใกล้ที่ใช้งานแล้วมันมักจะจัดการได้ง่าย.

เริ่มจากเล็ก ๆ: useState ในคอมโพเนนต์ใกล้ที่สุด. เมื่อสร้างหน้าจอ (เช่น ด้วย Koder.ai) มักจะแทบผลักทุกอย่างเข้า global store "เผื่อไว้" — นั่นแหละคือจุดที่คุณจะได้ store ที่ไม่มีใครเข้าใจ.

กฎการยก state แบบง่าย

ย้าย state ขึ้นเฉพาะเมื่อคุณตั้งชื่อปัญหาการแชร์ได้:

  • เก็บ state เฉพาะ UI ไว้ท้องถิ่นเป็นดีฟอลต์
  • ยกขึ้นไป parent ที่ใกล้ที่สุดเมื่อพี่น้องต้องใช้
  • ใช้ store เล็ก ๆ เมื่อหลาย route หรือคอมโพเนนต์ระยะไกลต้องค่าเดียวกันพร้อมกัน

ตัวอย่าง: ตารางที่มีแผงรายละเอียดสามารถเก็บ selectedRowId ไว้ในคอมโพเนนต์ตาราง หาก toolbar ในอีกส่วนของหน้าอยากใช้ ให้ยกขึ้นเป็น state ของหน้าดังกล่าว ถ้ารูตแยกเช่น bulk edit ต้องการ ก็ถึงเวลาสร้าง store เล็ก ๆ

รูปร่างของ store ที่อ่านง่าย

ถ้าใช้ store (เช่น Zustand) ให้โฟกัสงานเดียว เก็บ "อะไร" (selected IDs, filters) ไม่ใช่ "ผลลัพธ์" (รายการเรียง) ที่คุณสามารถ derive ได้.

เมื่อ store เริ่มโต ให้ถาม: นี่ยังเป็นฟีเจอร์เดียวไหม? ถ้าคำตอบคือ "กึ่ง ๆ" ให้แยกตอนนี้ ก่อนที่ฟีเจอร์ถัดไปจะทำให้มันกลายเป็นก้อน state ที่คุณกลัวจะเข้าไปแตะ.

ฟอร์ม ร่าง และ state ชั่วคราว

บั๊กฟอร์มมักมาจากการผสมกันของสามอย่าง: สิ่งที่ผู้ใช้พิมพ์, สิ่งที่เซิร์ฟเวอร์บันทึก, และสิ่งที่ UI แสดง.

สำหรับการจัดการ state ที่น่าเบื่อ ให้ถือฟอร์มเป็น client state จนกว่าจะ submit. ข้อมูลเซิร์ฟเวอร์คือเวอร์ชันที่บันทึกล่าสุด ฟอร์มคือร่าง อย่าแก้อ็อบเจ็กต์เซิร์ฟเวอร์โดยตรง คัดลอกค่าเข้า draft state ให้ผู้ใช้แก้ แล้ว submit และ refetch (หรืออัปเดต cache) เมื่อสำเร็จ.

ตัดสินใจตั้งแต่ต้นว่าจะให้บางค่าอยู่ต่อเมื่อผู้ใช้ไปหน้าอื่นหรือไม่ การเลือกแค่นั้นช่วยป้องกันบั๊กได้มาก ตัวอย่าง: โหมดแก้ไขแบบ inline และ dropdown เปิดควรรีเซ็ต ส่วน wizard ยาวหรือร่างข้อความที่ยังไม่ส่งอาจต้องเก็บไว้ การเก็บข้าม reload ควรทำเฉพาะเมื่อผู้ใช้คาดหวังจริง ๆ (เช่น checkout).

เก็บกฎการ validate ไว้ที่เดียว ถ้ากระจายไปตาม input, submit handlers และ helpers คุณจะได้ error ที่ไม่ตรงกัน เลือก schema เดียว (หรือฟังก์ชัน validate() เดียว) แล้วให้ UI ตัดสินใจว่าเมื่อไรจะแสดง error (on change, on blur, หรือ on submit).

ตัวอย่าง: สร้างหน้าจอ Edit Profile ด้วย Koder.ai. ดึง profile ที่บันทึกเป็น server state สร้าง draft ท้องถิ่นสำหรับฟิลด์ แสดง "unsaved changes" โดยเปรียบเทียบ draft กับ saved. ถ้าผู้ใช้ยกเลิก ให้ทิ้ง draft และแสดงเวอร์ชันจากเซิร์ฟเวอร์ ถ้าบันทึก สำเร็จแล้วแทนที่ saved ด้วย response จากเซิร์ฟเวอร์.

ขั้นตอนทีละขั้น: ทำให้การตั้งค่า state ที่ยุ่งเหยิงเรียบง่าย

Plan state before code
Agree on query keys, stores, and form drafts before you generate the next screen.

เมื่อแอปที่สร้างเติบโต มักจะจบลงด้วยข้อมูลเดียวกันในสามที่: component state, global store, และ cache การแก้ปัญหาไม่จำเป็นต้องห้องสมุดใหม่ แต่อยู่ที่การเลือกบ้านเดียวสำหรับแต่ละชิ้นของ state.

แนวทาง cleanup ที่ใช้ได้กับแอปส่วนใหญ่:

  1. สำรวจ state ของคุณ: จดสิ่งที่มี (lists, selected item, filters, modals, drafts) และติดป้ายว่าเป็น server หรือ client
  2. ลบสำเนา: เอา state อย่าง filteredUsers ออกถ้าคุณคำนวณได้จาก users + filter. ชอบ selectedUserId มากกว่าการทำสำเนา selectedUser
  3. รวมการ fetching ไว้ในชั้นเดียว: ใช้วิธี server-state เดียวเพื่อให้ caching, refetching, invalidation มีหนังสือกฎเล่มเดียว
  4. เพิ่ม store ท้องถิ่นเล็ก ๆ เมื่อมันสมควร (เช่น wizard draft ข้ามหน้า)
  5. ล็อกชื่อให้เป็นมาตรฐานเพื่อป้องกันความยุ่งเหยิงกลับมา

ตัวอย่าง: แอป CRUD ที่สร้างจาก Koder.ai มักเริ่มด้วย useEffect fetch บวกสำเนาใน global store หลังจากรวม server state รายการจะมาจาก query เดียว และ "refresh" กลายเป็น invalidation แทนการซิงก์ด้วยมือ.

สำหรับการตั้งชื่อ ให้คงที่และธรรมดา:

  • Queries: users.list, users.detail(id)
  • Client state: ui.isCreateModalOpen, filters.userSearch
  • Actions: openCreateModal(), setUserSearch(value)
  • Server writes: users.create, users.update, users.delete

เป้าหมายคือแหล่งความจริงหนึ่งแหล่งต่อสิ่งหนึ่ง พร้อมขอบเขตที่ชัดเจนระหว่าง server state และ client state.

สัญญาณแต่เนิ่นว่าความซับซ้อนของ state กำลังจะระเบิด

ปัญหา state เริ่มเล็ก ๆ แล้ววันหนึ่งคุณเปลี่ยนฟิลด์เดียวและสามส่วนของ UI ไม่เห็นพ้องกันเกี่ยวกับค่าที่ "แท้จริง".

สัญญาณชัดเจนที่สุดคือข้อมูลซ้ำ: ผู้ใช้หรือตะกร้าสินค้ามีอยู่ในคอมโพเนนต์, global store และ request cache แต่ละสำเนาอัปเดตต่างเวลา และคุณเพิ่มโค้ดเพียงเพื่อให้มันเท่ากัน.

สัญญาณอีกอย่างคือโค้ดซิงก์: effects ที่ผลัก state ไปมา รูปแบบเช่น "เมื่อ query เปลี่ยน ให้อัปเดต store" และ "เมื่อ store เปลี่ยน ให้ refetch" อาจใช้ได้จนกว่าจะเจอ edge case ที่ทำให้ค่าเป็น stale หรือเกิดลูป.

สัญญาณแดงเร็ว ๆ:

  • อธิบายไม่ได้ว่า "ค่าตัวนี้มาจากไหน" และ "ใครเป็นเจ้าของ" ในประโยคเดียว
  • store หรือ reducer นำเข้า API clients หรือตัวจัดการสถานะ HTTP
  • แต่ละหน้าจอใหม่เพิ่ม flags ร่วมเช่น needsRefresh, didInit, isSaving ที่ไม่มีใครลบทิ้ง
  • คุณเขียน effects เพื่อ mirror state ระหว่างชั้นต่าง ๆ
  • แก้บั๊กต้องอัปเดตฟิลด์เดียวกันในหลายชั้น

ตัวอย่าง: คุณสร้างแดชบอร์ดด้วย Koder.ai แล้วเพิ่ม modal แก้ไขโปรไฟล์ ถ้าข้อมูลโปรไฟล์อยู่ใน query cache, คัดลอกเข้า global store, แล้วซ้ำใน local form state คุณมีสามแหล่งความจริง เมื่อตั้ง refetch เบื้องหลังหรือ optimistic updates ความไม่ตรงกันจะปรากฏขึ้น.

เมื่อเห็นสัญญาณเหล่านี้ การเคลื่อนไหวที่น่าเบื่อคือเลือกเจ้าของเดียวสำหรับแต่ละข้อมูลแล้วลบสำเนา.

กับดักทั่วไปและวิธีหลีกเลี่ยง

Build a boring React base
Generate a React app from chat, then keep server and UI state boundaries clear from day one.

เก็บของไว้ "เผื่อไว้" เป็นวิธีที่เร็วที่สุดในการทำให้ state เจ็บปวด โดยเฉพาะในแอปที่สร้างอัตโนมัติ.

คัดลอก response ของ API เข้า global store เป็นกับดักทั่วไป ถ้าข้อมูลมาจากเซิร์ฟเวอร์ (lists, details, profile) อย่าคัดลอกเข้า client store โดยค่าเริ่มต้น เลือกบ้านเดียวสำหรับ server data (มักเป็น query cache). ใช้ client store สำหรับค่า UI เท่านั้นที่เซิร์ฟเวอร์ไม่รู้จัก.

เก็บค่าที่ derived เป็นกับดักอีกอย่าง ยอด, รายการกรอง, canSubmit, isEmpty ควรถูกคำนวณจาก input ถ้าประสิทธิภาพกลายเป็นปัญหาจริง ๆ ให้ memoize ภายหลัง แต่ไม่เริ่มจากการเก็บผลลัพธ์.

หนึ่ง mega-store สำหรับทุกอย่าง (auth, modals, toasts, filters, drafts, onboarding flags) จะกลายเป็นที่ทิ้ง แบ่งตามขอบเขตฟีเจอร์ ถ้า state ใช้โดยหน้าจอเดียว ให้เก็บท้องถิ่น.

Context ดีสำหรับค่าคงที่ (theme, current user id, locale). สำหรับค่าที่เปลี่ยนบ่อย มันอาจทำให้เกิดการ re-render กว้าง ใช้ Context สำหรับการเดินสาย ส่วน state ที่เปลี่ยนบ่อยให้ใช้ component state หรือ store เล็ก ๆ.

สุดท้าย หลีกเลี่ยงการตั้งชื่อไม่สอดคล้องกัน keys query ใกล้เคียงและฟิลด์ store เกือบเหมือนกันจะสร้างการซ้ำแบบละเอียด เลือกมาตรฐานง่าย ๆ และทำตามมัน.

เช็คลิสต์ด่วนก่อนเพิ่ม state

เมื่ออยากเพิ่ม "แค่ตัวแปร state อีกตัว" ให้ทำ ownership check สั้น ๆ:

แรก คุณชี้ได้ไหมว่ามีที่เดียวที่ทำการ fetching และ caching (เครื่องมือ query เดียว, ชุด query keys เดียว)? ถ้าข้อมูลเดียวถูก fetch ในหลายคอมโพเนนต์และก็ถูกคัดลอกเข้า store คุณกำลังจ่ายดอกเบี้ยอยู่แล้ว.

ที่สอง ค่านี้จำเป็นแค่ในหน้าจอเดียวไหม (เช่น "panel filter เปิด" )? ถ้าใช่ มันไม่ควรเป็น global.

ที่สาม เก็บ ID แทนการคัดลอกอ็อบเจ็กต์ได้ไหม? เก็บ selectedUserId แล้วอ่านผู้ใช้จาก cache หรือ list.

ที่สี่ มันเป็น derived ไหม? ถ้าคำนวณจาก state ที่มีอยู่ อย่าเก็บ.

สุดท้าย ทำการทดสอบ trace หนึ่งนาที ถ้าทีมเมทตอบไม่ได้ว่า "ค่านี้มาจากไหน? (prop, local state, server cache, URL, store)" ในไม่กี่วินาที ให้แก้เจ้าของก่อนเพิ่ม state ต่อ.

คำถามที่พบบ่อย

What’s the simplest way to untangle messy React state?

เริ่มจากการติดป้ายให้ชัดกับทุกชิ้นของ state ว่าเป็น server, client (UI) หรือ derived.

  • Server: ข้อมูลที่ดึงมาจาก backend เช่น users, tasks, permissions.
  • Client: ตัวเลือกของ UI เช่น "modal เปิดอยู่", แท็บที่เลือก, ข้อความตัวกรอง.
  • Derived: ค่าที่คำนวณจากค่าอื่น (เช่น รายการที่ถูกกรอง, ยอดรวม, isValid).

เมื่อแยกแล้ว ให้แน่ใจว่าแต่ละรายการมี เจ้าของที่ชัดเจนเพียงที่เดียว (query cache, state ท้องถิ่นของคอมโพเนนต์, URL หรือ store ขนาดเล็ก).

How do I tell if something is server state or client state?

ใช้การทดสอบง่าย ๆ: “ถ้าฉันรีเฟรชหน้าแล้วสร้างใหม่จากเซิร์ฟเวอร์ได้ไหม?”

  • ถ้า ใช่ ให้ถือเป็น server state (ต้อง fetch/cache/refetch).
  • ถ้า ไม่ ให้ถือเป็น client state (state ท้องถิ่นหรืออาจเป็น store).

ตัวอย่าง: รายการโปรเจกต์เป็น server state; selected row ID เป็น client state.

Why is copying API data into local or global state such a big problem?

เพราะมันสร้าง แหล่งข้อมูลที่เป็นจริงสองที่.

ถ้าคุณ fetch users แล้วคัดลอกเข้า useState หรือ global store คุณต้องคอยซิงก์ระหว่าง:

  • การ refetch เบื้องหลัง
  • การอัปเดตจากแท็บ/ผู้ใช้คนอื่น
  • อัปเดตบางส่วนหลังการเขียนข้อมูล

กฎพื้นฐาน: อ่านข้อมูลเซิร์ฟเวอร์จากที่เดียว (query cache) และสร้าง state ท้องถิ่นแค่สำหรับเรื่อง UI หรือ draft เท่านั้น.

When is it okay to store derived values instead of computing them?

เก็บค่าที่ derived ไว้ก็ต่อเมื่อคุณ คำนวณมันไม่ไหวอย่างคุ้มค่า ด้วยทางเลือกอื่น.

ปกติให้คำนวณจาก input ที่มีอยู่:

  • visibleUsers = users.filter(...)
  • total = items.reduce(...)
  • canSubmit = isValid && !isSaving

ถ้ามีปัญหาด้านประสิทธิภาพจริง ๆ ให้วัดก่อน แล้วค่อยใช้ useMemo หรือปรับโครงสร้างข้อมูล แทนการเริ่มด้วยการเก็บค่าที่อาจล้าสมัย.

What’s a “boring” way to handle server state in React?

โดยปกติ: เลือกเครื่องมือสำหรับ server state ตัวเดียวแล้วใช้มันเป็นมาตรฐาน สำหรับหลายแอป React, TanStack Query มักจะพอ.

หลักการง่าย ๆ:

  • คอมโพเนนต์ขอข้อมูล แสดงสถานะ loading/error และไม่ต้องสนใจว่ามีการ fetch กี่ครั้งใต้ผิวน้ำ
  • ใช้ query keys เป็นระบบการตั้งชื่อ: ให้คงที่, ใส่แค่พารามิเตอร์ที่เปลี่ยนผลลัพธ์ (filters, page, sort)
  • สำหรับการเขียน ให้ใช้ mutations และกำหนดชัดเจนว่าจะทำอะไรต่อเมื่อสำเร็จ (invalidate หรืออัปเดต cache แบบระบุเป้าหมาย)

อยากจะ refetch() หลายที่ให้หยุดก่อน — เลือกวิธีที่น่าเบื่อแต่ชัดเจนแทน:

When should I move UI state into a global store?

เก็บไว้ ท้องถิ่น จนกว่าจะมีความต้องการแชร์ที่ชัดเจน.

กฎการยก state ขึ้น:

  • เริ่มที่ useState ในคอมโพเนนต์ที่ใกล้ที่สุด
  • ยกขึ้นเฉพาะเมื่อพี่น้องต้องใช้ร่วมกัน
  • ใช้ store เล็ก ๆ ก็ต่อเมื่อคอมโพเนนต์ระยะไกลหรือหลาย route ต้องการค่าเดียวกันพร้อมกัน

วิธีนี้จะกันไม่ให้ global store กลายเป็นที่ทิ้งของ flag สุ่ม ๆ

Should I store the selected object or just its ID?

เก็บ ID และ flag เล็ก ๆ แทนการเก็บอ็อบเจ็กต์ทั้งก้อน.

ตัวอย่าง:

  • selectedUserId
  • selectedUser (อ็อบเจ็กต์ที่คัดลอกมา)

จากนั้นเมื่อเราจะแสดงรายละเอียด ให้ค้นหาผู้ใช้จาก cached list/detail query การ refetch เบื้องหลังจะอัปเดต UI ได้โดยไม่ต้องซิงก์เพิ่ม.

What’s the cleanest way to manage forms and drafts?

มองแบบ practical:

  • ดึงระเบียนที่บันทึกไว้เป็น server state
  • สร้าง draft ท้องถิ่นจากมัน
  • ให้ผู้ใช้แก้ไข draft ได้อย่างอิสระ
  • เมื่อบันทึก ส่ง draft แล้ว refresh/อัปเดต cache
  • เมื่อยกเลิก ให้ทิ้ง draft แล้วแสดงค่าจาก server

แบบนี้จะไม่เผลอแก้ข้อมูล server โดยตรงและไม่สู้กับการ refetch.

What are early warning signs that state complexity is about to explode?

สัญญาณเตือน:

  • ข้อมูลเดียวกันอยู่ในคอมโพเนนต์, global store และ query cache
  • มี effects ที่พยายามซิงก์ state สลับไปมา ("เมื่อ query เปลี่ยน ให้ set store", "เมื่อ store เปลี่ยน ให้ refetch")
  • flags ทั่วไปสะสมอย่าง needsRefresh, didInit, isSaving
  • อธิบายไม่ได้ว่า "ค่านี้มาจากไหน" ในประโยคเดียว

การแก้มักไม่ต้องหาคลังเครื่องมือใหม่ แต่อยู่ที่ลบสำเนาและเลือกเจ้าของเดียวต่อค่าที่สำคัญ.

How do I keep state “boring” when using a generator like Koder.ai?

หน้าจอที่สร้างโดยเครื่องมือมักจะเดินไปผิดทางเร็ว ๆ ให้ตั้งมาตรฐานการเป็นเจ้าของ:

  • Server state: ผ่านเลเยอร์ fetch/cache เดียวกันเสมอ
  • Client UI state: ท้องถิ่นโดยดีฟอลต์, ย้ายขึ้นเฉพาะเมื่อจำเป็น
  • Derived values: คำนวณ ไม่เก็บ

ถ้าใช้ Koder.ai, ใช้ Planning Mode เพื่อคุยเรื่องขอบเขตก่อนสร้างหน้าจอใหม่ และใช้ snapshot/rollback เมื่อต้องทดลองการเปลี่ยนแปลง state เพื่อย้อนกลับได้ง่ายถ้าผิดพลาด.

Related posts