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 = จัดตามชุด (ชุดทำงาน/ชุดออกกำลังกาย)" — โปรเจกต์เล็กจัดตามชนิดก็พอหาเจอ โปรเจกต์ใหญ่จัดตามชุดจะหยิบใช้ ง่ายกว่าเพราะของที่เกี่ยวข้องกันอยู่ด้วยกัน
สัญญาณที่บอกว่าถึงเวลาต้องย้ายจาก "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 rulerules-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) คลิก"
การตั้งชื่อ 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 typeReactNodeไม่ใช่any/JSX.Elementเฉยๆ (ครอบคลุม string, number, fragment, null ด้วย)
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.