Context không có shallow compare — bất kỳ consumer nào cũng re-render khi value object đổi reference, kể cả khi field họ đọc không đổi.
Anti-pattern phổ biến:
function AuthProvider({ children }) {
const [user, setUser] = useState(null)
const [theme, setTheme] = useState('dark')
// value mới mỗi render → mọi consumer re-render
return (
<AuthContext.Provider value={{ user, setUser, theme, setTheme }}>
{children}
</AuthContext.Provider>
)
}Vấn đề: child chỉ cần theme cũng re-render khi user đổi.
Cách fix:
1. Tách context: AuthContext riêng, ThemeContext riêng. Đơn giản và hiệu quả.
2. Memo hóa value: useMemo(() => ({ user, setUser }), [user]).
3. Tách "value" và "setter" thành 2 context: một cho data (đổi nhiều), một cho action (stable). Pattern Kent C. Dodds đề xuất.
4. Dùng store ngoài (Zustand) thay context cho global state — selector có shallow compare built-in.
Hệ quả trên RN nặng hơn web: mỗi re-render → bridge serialize → UIView update. List 100 row mà có context không tách → mỗi keystroke trong TextInput re-render hết list.