Skip to main content

React Best Practices & Code Review Checklist

Project Structure​

นิยม 2 แนวทางหลัก: group by file type (components/, hooks/, utils/ แยกตามประเภท ไฟล์) เหมาะกับโปรเจกต์เล็ก และ group by feature (features/orders/, features/users/ แต่ละ feature มี component/hook/util ของตัวเองอยู่ด้วยกัน) เหมาะกับโปรเจกต์ใหญ่ที่มีหลายทีม ดูแลคนละส่วน

🧠 จำง่ายๆ

"แยกตามประเภท = จัดตู้เสื้อผ้าตามชนิด (เสื้อ/กางเกง/ถุงเท้า), แยกตาม feature = จัดตามชุด (ชุดทำงาน/ชุดออกกำลังกาย)" — โปรเจกต์เล็กจัดตามชนิดก็พอหาเจอ โปรเจกต์ใหญ่จัดตามชุดจะหยิบใช้ ง่ายกว่าเพราะของที่เกี่ยวข้องกันอยู่ด้วยกัน

🎯 Beyond: จุดที่ชาวบ้านไม่ค่อยรู้

สัญญาณที่บอกว่าถึงเวลาต้องย้ายจาก "group by type" ไป "group by feature" ไม่ใช่แค่ "โปรเจกต์ ใหญ่ขึ้น" แต่คือการเปลี่ยนแปลงต้องแก้ไฟล์กระจายหลายโฟลเดอร์พร้อมกันบ่อยๆ (เช่น เพิ่ม feature "export order" ต้องไปแก้ components/, hooks/, utils/, types/ พร้อมกันทุกครั้ง) หลักการที่เรียกว่า "screaming architecture" (โครงสร้างโฟลเดอร์ควร "ตะโกนบอก" ว่าแอปนี้ทำ อะไร ไม่ใช่บอกว่าใช้ framework/pattern อะไร) สนับสนุนแนวทาง group-by-feature เพราะเปิดโฟลเดอร์ features/ แล้วเห็นทันทีว่าแอปมี capability อะไรบ้าง ต่างจาก group-by-type ที่เปิดมาเห็นแค่ "นี่คือ React app" (รู้แค่ tech stack ไม่รู้ domain)

Naming Convention​

  • Component: PascalCase (UserCard, OrderList)
  • Hook: ขึ้นต้นด้วย use เสมอ (useAuth, useDebounce) — เป็นกฎบังคับไม่ใช่แค่ convention เพราะ ESLint rule rules-of-hooks ใช้ชื่อที่ขึ้นต้นด้วย use ตัดสินใจว่าจะ apply rule ตรวจสอบ hook กับฟังก์ชันนั้นหรือไม่
  • Event handler prop: onX (onClick, onSave), handler function ภายใน: handleX (handleClick, handleSave)
🧠 จำง่ายๆ

"on = ชื่อ event ที่รับฟัง, handle = ชื่อฟังก์ชันที่ทำงานจริง" — <Button onClick= {handleClick}> อ่านเป็นประโยคได้เลย: "เมื่อ (on) คลิก ให้จัดการ (handle) คลิก"

🎯 Beyond: จุดที่ชาวบ้านไม่ค่อยรู้

การตั้งชื่อ hook ขึ้นต้นด้วย use ไม่ใช่แค่ convention เพื่อความสวยงาม — ESLint plugin eslint-plugin-react-hooks ใช้ regex pattern ตรวจชื่อฟังก์ชัน (/^use[A-Z]/) เป็นเกณฑ์ เดียวในการตัดสินว่าฟังก์ชันนี้คือ "hook" ที่ต้องเช็ค Rules of Hooks (top-level only, ลำดับ คงที่) หรือเป็นแค่ฟังก์ชันธรรมดา ถ้าตั้งชื่อ custom hook ว่า getUserData() แทนที่จะเป็น useUserData() แม้ข้างในจะเรียก useState/useEffect จริงๆ ESLint จะไม่เตือนเลยถ้าเขียน ผิดกฎ Rules of Hooks เพราะมันไม่รู้จักฟังก์ชันนี้ว่าเป็น hook — เป็นเหตุผลทางเทคนิคที่แท้จริง เบื้องหลัง naming convention ที่ "ดูเหมือน" แค่สไตล์การตั้งชื่อ

Code Review Checklist​

  • ไม่มี useEffect ที่ขาด dependency ที่ควรมี (เช็คด้วย eslint-plugin-react-hooks ใน CI)
  • List ที่ render ด้วย .map() มี key ที่เป็น stable unique ID ไม่ใช่ index (ยกเว้น list นั้นไม่มีวันเรียงลำดับใหม่/กรอง/เพิ่มลบกลาง list)
  • ไม่มี state ที่ควรเป็น derived value (คำนวณจาก state อื่นได้) แต่ดันเก็บเป็น state แยกต่างหากแล้วพยายาม sync ด้วย useEffect — ควรคำนวณตรงๆ ตอน render แทน
  • Event handler ที่ส่งเป็น prop ลง component ลูกที่ทำ React.memo ไว้ ถูก wrap ด้วย useCallback แล้ว (ไม่งั้น memo ไม่มีผล)
  • ไม่มี side effect (API call, subscribe) ที่เขียนอยู่นอก useEffect ตรงๆ ใน render body
  • Component ที่รับ children ใช้ TypeScript type ReactNode ไม่ใช่ any/JSX.Element เฉยๆ (ครอบคลุม string, number, fragment, null ด้วย)
English Summary

Group-by-type folder structure suits small projects; group-by-feature ("screaming architecture") suits larger ones where changes frequently touch multiple files across one feature. Naming conventions aren't just style: hooks must start with use because eslint-plugin-react-hooks uses that exact naming pattern (not actual hook usage) to decide whether to enforce the Rules of Hooks on a function — naming it wrong silently disables the linting safety net. Review checklist: complete useEffect dependencies, stable list keys, deriving computed values during render instead of syncing duplicate state via effects, pairing useCallback with React.memo'd children, keeping side effects inside useEffect rather than directly in the render body, and typing children as ReactNode.