useContext đọc một giá trị mà component cha cung cấp, nhờ đó một component nằm sâu trong cây nhận được nó mà không cần mọi component ở giữa truyền nó qua prop. Bạn tạo context bằng createContext, bọc một phần của cây trong provider có value, rồi gọi useContext(TheContext) ở bất cứ đâu cần giá trị đó.
Toolbar không hề nhắc tới theme, vậy mà cả hai nút đều thay đổi khi bạn tích vào ô. Đó chính là ý nghĩa của context: các component ở giữa không phải mang một prop mà chúng không dùng, đúng là vấn đề prop drilling mà context giải quyết.
Ba bước
// 1. Create it once, outside any component, usually in its own file
export const ThemeContext = createContext('light');
// 2. Provide a value to a part of the tree
<ThemeContext value={theme}>
<Page />
</ThemeContext>
// 3. Read it in any component inside that part
const theme = useContext(ThemeContext);
Trong React 19, chính object context là provider. Code cũ hơn viết <ThemeContext.Provider value={theme}>, cách này vẫn hoạt động, nên bạn sẽ gặp cả hai.
useContext tìm ngược lên cây để lấy provider gần nhất của context đó. Một component đọc provider gần nó nhất, nên bạn có thể lồng các provider để ghi đè giá trị cho một khu vực.
Giá trị mặc định
Đối số truyền cho createContext là giá trị useContext trả về khi không có provider nào phía trên component. Nó không thay đổi khi state thay đổi; nó chỉ là giá trị dự phòng.
Greeting đầu tiên không có provider nào phía trên và nhận giá trị mặc định 'en'. Cái thứ hai đọc được 'es', còn cái thứ ba đọc 'ja' ở gần hơn. Đổi value="es" bên ngoài thành value="ja" và chỉ dòng thứ hai thay đổi.
Cập nhật context từ component con
Context chỉ truyền giá trị xuống dưới. Để một component con nằm sâu có thể thay đổi nó, hãy giữ giá trị trong state ở chỗ đặt provider, và đặt hàm set vào context ngay cạnh giá trị.
LoginButton gọi setUser, state trong App thay đổi, provider nhận giá trị mới, và Header cũng cập nhật theo. Không component nào nhận prop cả.
Mọi component đọc context đều render lại khi giá trị thay đổi
Khi value của provider thay đổi, React render mọi component gọi useContext cho nó, kể cả khi component đó nằm trong memo và props không đổi. React so sánh giá trị cũ và mới bằng Object.is. Vì vậy value={{ user, setUser }} là một object mới ở mỗi lần App render, và mọi component đọc context đều render lại kể cả khi user không thay đổi.
Cả hai component đọc đều ghi log một lần ở lần render đầu. Click nút: chỉ PlainReader ghi log lại, vì context của nó nhận một object mới. useMemo đưa cho MemoContext cùng một object cho đến khi name thay đổi, nên MemoReader đứng yên. Điều này chỉ quan trọng khi việc render bắt đầu chậm; với một ứng dụng nhỏ, tạo object mới ở mỗi lần render hoàn toàn ổn.
Tách context
Một giá trị thay đổi thường xuyên và một giá trị không bao giờ thay đổi không nên dùng chung một context. Nếu hàm set nằm chung với state, một component chỉ cần gọi setUser vẫn render lại mỗi khi user thay đổi. Hãy đặt chúng vào hai context, khi đó mỗi component chỉ đăng ký theo dõi đúng thứ nó đọc:
const UserContext = createContext(null);
const SetUserContext = createContext(() => {});
function UserProvider({ children }) {
const [user, setUser] = useState(null);
return (
<SetUserContext value={setUser}>
<UserContext value={user}>{children}</UserContext>
</SetUserContext>
);
}
setUser từ useState giữ nguyên danh tính mãi mãi, nên các component chỉ đọc SetUserContext không bao giờ render lại vì một lần đăng nhập. Ý tưởng này cũng áp dụng cho dữ liệu không liên quan đến nhau: theme và giỏ hàng nên nằm trong hai context riêng, không phải một AppContext khổng lồ.
Một bước hoàn thiện phổ biến là một custom hook như useUser() gọi useContext(UserContext) và ném ra một lỗi rõ ràng khi thiếu provider, để bên gọi không bao giờ phải import trực tiếp object context.
Context, props hay thư viện quản lý state
- Props là lựa chọn mặc định. Truyền một giá trị xuống hai ba cấp vẫn rõ ràng và dễ lần theo.
- Context hợp với những giá trị mà nhiều component ở nhiều độ sâu cần đến và ít khi thay đổi: theme, ngôn ngữ, người dùng đang đăng nhập, một object feature flag.
- Thư viện quản lý state (Redux Toolkit, Zustand, Jotai và các thư viện khác) hợp với state lớn, cập nhật thường xuyên, khi bạn muốn một component chỉ đăng ký theo dõi một phần và bỏ qua các lần render do phần còn lại gây ra. Context không có selector như vậy: một component đọc context sẽ render lại khi giá trị có bất kỳ thay đổi nào.
Trước khi dùng context hay một thư viện, hãy xem liệu truyền component qua children có loại bỏ được prop drilling không. Thường là có, mà không cần thêm API mới nào.
Câu hỏi thường gặp
useContext trong React dùng để làm gì?
Nó trả về giá trị hiện tại của một context, lấy từ provider gần nhất phía trên component trong cây. Khi giá trị của provider đó thay đổi, mọi component đọc nó đều render lại.
Giá trị mặc định trong createContext dùng để làm gì?
Đó là giá trị useContext trả về khi không có provider nào phía trên component. Nó hữu ích cho việc test và cho các component được render riêng lẻ, và nó không bao giờ thay đổi.
React 19 còn cần Context.Provider không?
Không. Trong React 19, bạn có thể render chính context làm provider: <ThemeContext value="dark">. <ThemeContext.Provider value="dark"> vẫn hoạt động, nên code cũ vẫn chạy bình thường.
Làm sao cập nhật giá trị context từ component con?
Giữ giá trị trong state ở component render provider, và đặt hàm set vào context cạnh giá trị đó: value={{ theme, setTheme }}. Component con đọc setTheme bằng useContext và gọi nó.
Context trong React có thay thế được Redux không?
Context truyền một giá trị xuống cây; tự nó không quản lý state. Kết hợp với useState hoặc useReducer, nó đủ cho nhiều ứng dụng. Một thư viện quản lý state bổ sung những thứ context không có, như đăng ký theo dõi một phần state để các cập nhật không liên quan không làm component render lại, cùng với devtools và middleware.