Every React developer eventually runs into the same wall. You fetch a user object inside your top-level App component. Then you pass it down. And down again. Through a route wrapper, through a layout shell, through a sidebar container, just so a tiny avatar component three layers deep can display a profile picture. The components in the middle do not care about that user object. They are just forwarding the parcel. That is prop drilling, and it turns a clean component tree into a frustrating game of telephone.
The real pain starts when the shape of that data changes. Maybe the backend starts nesting user.profile.avatar instead of user.avatar. Suddenly you are editing TypeScript interfaces or PropTypes across five files that never use the data themselves. That is where the React Context API steps in.
How Context Rewires the Data Flow
Think of Context as a WiFi router sitting at the center of your home. Without it, you would need Ethernet cables snaking through every room to get a signal to your laptop. With it, the router broadcasts through the air, and any device with the right password can connect directly. The walls do not matter.
In React terms, the root of your app can broadcast data through the component tree without asking every layer to act as a courier. Any nested component can subscribe to that broadcast and receive exactly what it needs.
The Three Core Pieces
Context API boils down to three moving parts.
React.createContext() sets up the broadcast channel. It returns an object containing a Provider and (in older code) a Consumer. You only need to call this once for a given feature.
The Provider is a component that wraps a section of your tree. It accepts one prop called value. Whatever you place in that prop becomes available to every descendant, no matter how deep they sit.
useContext is the Hook that lets a function component tap into that broadcast. Inside your component, you pass the context object you created into useContext, and it returns the current value. That is it. No wrappers, no extra props.
Before Hooks arrived, you had to use the Consumer pattern with render props. It worked, but it created a lot of indentation and wrapper clutter. useContext flattened all of that into a single line inside your function body.
When Context Actually Makes Sense
Do not reach for Context out of habit. It is built for data that many unrelated components share across different branches of your tree. Good candidates include:
- Theme settings. Not just light or dark mode, but spacing tokens, color palettes, and font scales. Manually threading these through every styled button and modal gets old fast.
- User authentication. Login status, a permissions array, or the current user object. Your header bar, a dashboard widget, and a private route guard might all live in different corners of the tree.
- Language preferences. Locale strings, date formats, and currency symbols. Deep leaf components like form labels need these without every parent on the path knowing about them.
- Shopping cart data. Item count, total value, and add-to-cart functions. The header badge and the checkout page need the same state, but they usually sit under completely different layout branches.
A Practical Theme Switcher
One of the clearest ways to see Context in action is a theme toggle. Here is how you might wire it up without skipping the details that actually matter.
First, create a ThemeContext.js file. Call React.createContext() and store the result. Then build a ThemeProvider component that manages the current theme with useState or useReducer. Wrap the children in your context's Provider, passing an object that contains both the current theme and a function to toggle it. Export both the ThemeProvider and the context object itself.
Second, go to your app entry point. Import ThemeProvider and wrap your entire application with it. If you skip this step, anything trying to read the context later will only see the default value.
Third, inside a Header or Content component, import the context object and useContext. Call the Hook, destructure the theme and the toggle function, and apply your CSS classes conditionally. Add a button that calls the toggle. The component never receives a theme prop from its parent. It pulls the signal straight from the air.
Prop Drilling, Context, or Redux?
การเลือกใช้เครื่องมือเหล่านี้ไม่ใช่เรื่องของความภักดีต่อเครื่องมือใดเครื่องมือหนึ่ง แต่เป็นเรื่องของรูปแบบของ state ของคุณมากกว่า
Prop drilling นั้นใช้งานได้ดีมากหากมีความลึกเพียงสองหรือสามระดับ เพราะมันมีความชัดเจน ไล่สายตาดูใน IDE ได้ง่าย และทำให้เห็น dependencies ได้อย่างชัดเจน ปัญหาจะเริ่มปรากฏขึ้นก็ต่อเมื่อคุณเริ่มส่ง prop ตัวเดิมผ่านเลเยอร์ต่างๆ ไปถึงหกหรือเจ็ดชั้น
Context API มาพร้อมกับ React เลย ซึ่งหมายความว่าไม่ต้องเพิ่มขนาด bundle และไม่ต้องตั้งค่าภายนอก มันจัดการกับ global state ขนาดเล็กถึงกลางได้อย่างยอดเยี่ยม โดยเฉพาะข้อมูลที่ไม่ได้เปลี่ยนแปลงบ่อย เช่น ธีม (themes) หรือโปรไฟล์ผู้ใช้ (user profiles)
Redux จำเป็นต้องติดตั้งไลบรารีเพิ่มเติมและต้องเขียน boilerplate เยอะหน่อย แต่มันจะคุ้มค่าก็ต่อเมื่อ logic ของ state มีความซับซ้อน เมื่อ state หลายๆ ส่วน (slices) มีการปฏิสัมพันธ์กันอย่างลึกซึ้ง หรือเมื่อคุณต้องการ time-travel debugging และ middleware สำหรับข้อมูล global แบบง่ายๆ Redux ถือว่าเกินความจำเป็น (overkill)
ความเป็นจริงด้านประสิทธิภาพที่ไม่มีใครพูดถึง
นี่คือจุดสำคัญที่แยกความแตกต่างระหว่างการเขียนแบบ junior กับ senior เมื่อค่าใน Context Provider เปลี่ยนแปลง ทุกๆ component ที่ใช้งาน context นั้นจะเกิดการ re-render ใหม่ โดยไม่สนใจว่าข้อมูลส่วนที่ component นั้นสนใจจะยังเหมือนเดิมหรือไม่ React จะเห็น reference ใหม่และสั่งให้มีการอัปเดตทันที
หากคุณเท state ทั้งหมดของแอปพลิเคชันลงใน StoreContext ขนาดมหึมาเพียงอันเดียว เท่ากับว่าคุณได้เชื่อม UI ทั้งหมดเข้าด้วยกัน การเปลี่ยนการตั้งค่าธีมเพียงอย่างเดียวจะทำให้ shopping cart, กราฟใน dashboard และรายการแจ้งเตือนของคุณต้อง re-render ใหม่ทั้งหมด ซึ่งเป็นการทำงานที่สิ้นเปลืองโดยไม่จำเป็น
ควรแยก context ตามโดเมน (domain) เช่น ใช้ ThemeContext สำหรับการตั้งค่าการแสดงผล, UserContext สำหรับข้อมูลโปรไฟล์ และ CartContext สำหรับสถานะการซื้อขาย หากผู้ใช้แก้ไขชื่อที่แสดง (display name) ส่วน header ของคุณจะอัปเดตโดยไม่ไปกระทบกับ product grid นอกจากนี้ ควรระวังสิ่งที่คุณส่งเข้าไปใน prop value ของ Provider หากคุณส่ง object literal อย่าง { theme, toggleTheme } เข้าไปตรงๆ ระหว่างการ render คุณจะสร้าง reference ใหม่ในทุกๆ การ render และกระตุ้นให้เกิดการอัปเดตที่ไม่จำเป็น ควรทำให้ค่าเหล่านั้นคงที่ด้วย useMemo หากค่านั้นประกอบด้วย function หรือข้อมูลที่ไม่ใช่ primitive
ข้อผิดพลาดที่ทำให้เสียเวลาเป็นชั่วโมง
มีข้อผิดพลาดสองอย่างที่ทีมพัฒนาเจอบ่อยครั้ง
การลืม export context object เป็นเรื่องง่ายที่จะ export แค่ component ThemeProvider แล้วพยายามเรียกใช้ useContext(ThemeProvider) แต่นั่นไม่ใช่สิ่งที่ถูกต้อง Hook ต้องการ context object ที่ได้มาจาก createContext ไม่ใช่ wrapper component หากคุณ export แค่ Provider ตัว consumer ก็จะไม่มีอะไรให้ import ไปใช้งาน
การเรียกใช้ useContext นอก Provider ตัว Hook จะคืนค่า default ที่คุณส่งให้ createContext หากคุณไม่ได้ส่งค่า default ไว้ คุณจะได้ undefined หาก component tree ของคุณ render ตัว consumer ไว้ในตำแหน่งที่สูงกว่า Provider ใน DOM หรือหากไม่มี Provider เลย ข้อมูลของคุณก็จะไม่ถูกส่งมาถึง ตรวจสอบให้แน่ใจว่าไฟล์ index หรือ root ของคุณได้ทำการ wrap แอปพลิเคชันไว้เรียบร้อยแล้ว
บทสรุปที่แท้จริง
React Context ไม่ใช่การปฏิวัติการจัดการ state แต่มันคือเครื่องมือที่ออกแบบมาเพื่อแก้ปัญหาเชิงโครงสร้างที่เฉพาะเจาะจง นั่นคือการส่งข้อมูลไปยัง component ที่อยู่ห่างไกลโดยไม่ต้องเปลี่ยนทุกเลเยอร์ให้กลายเป็นที่ทำการไปรษณีย์ จงใช้มันสำหรับข้อมูลที่เป็น global จริงๆ แยก context ตามโดเมนเพื่อรักษาประสิทธิภาพการ render และอย่าลืม wrap component tree ด้วย Provider ที่ถูกต้องก่อนที่จะพยายามอ่านค่าเสมอ หากคุณทำสิ่งเหล่านี้ให้เป็นนิสัย component tree ของคุณจะสะอาด รวดเร็ว และทำความเข้าใจได้ง่าย
