React - useContext: Data Sharing
When you need to pass data through 5-6 components, useContext will save your life. It's data sharing without the headache!
-> It can be used as a store, but be careful, everything inside is re-rendered on the first change.
So use it sparingly.
The Problem: Prop Drilling
The Hell of Prop Drilling
// ❌ NIGHTMARE - Prop drilling on 6 levels!
function App() {
const [user, setUser] = useState({ name: 'Andy', theme: 'light' })
const [cart, setCart] = useState([])
return (
<Layout user={user} setUser={setUser} cart={cart} setCart={setCart}>
<Dashboard user={user} setUser={setUser} cart={cart} setCart={setCart}>
<Sidebar user={user} setUser={setUser}>
<Profile user={user} setUser={setUser} />
</Sidebar>
<MainContent cart={cart} setCart={setCart}>
<ProductList cart={cart} setCart={setCart} />
<CartWidget cart={cart} />
</MainContent>
</Dashboard>
</Layout>
)
}
function Layout({ user, setUser, cart, setCart, children }) {
return (
<div>
<Header user={user} setUser={setUser} cart={cart} />
{children}
</div>
)
}
function Header({ user, setUser, cart }) {
return (
<header>
<UserInfo user={user} setUser={setUser} />
<CartIcon cart={cart} />
</header>
)
}
function UserInfo({ user, setUser }) {
return (
<div>
<span>Hello {user.name} !</span>
<ThemeToggle user={user} setUser={setUser} />
</div>
)
}
function ThemeToggle({ user, setUser }) {
const toggleTheme = () => {
setUser(prev => ({
...prev,
theme: prev.theme === 'light' ? 'dark' : 'light'
}))
}
return (
<button onClick={toggleTheme}>
{user.theme === 'light' ? '🌙' : '☀️'}
</button>
)
}
// 😵 Props passed everywhere!
// 🐛 Difficult to maintain!
// 💀 Intermediate components polluted!
The Solution: useContext
Creating a Context
import { createContext, useContext, useState } from 'react'
// 1. ✅ Create the Context
const UserContext = createContext()
// 2. ✅ Provider Component
function UserProvider({ children }) {
const [user, setUser] = useState({
name: 'Andy',
email: 'andy@example.com',
theme: 'light',
preferences: {
notifications: true,
language: 'fr'
}
})
// Functions to modify the user
const updateUser = useCallback((updates) => {
setUser(prev => ({ ...prev, ...updates }))
}, [])
const updatePreferences = useCallback((prefUpdates) => {
setUser(prev => ({
...prev,
preferences: { ...prev.preferences, ...prefUpdates }
}))
}, [])
const toggleTheme = useCallback(() => {
setUser(prev => ({
...prev,
theme: prev.theme === 'light' ? 'dark' : 'light'
}))
}, [])
// Memorize the value to avoid re-renders
const contextValue = useMemo(() => ({
user,
updateUser,
updatePreferences,
toggleTheme
}), [user, updateUser, updatePreferences, toggleTheme])
return (
<UserContext.Provider value={contextValue}>
{children}
</UserContext.Provider>
)
}
// 3. ✅ Custom hook to use the Context
function useUser() {
const context = useContext(UserContext)
if (!context) {
throw new Error('useUser must be used within a UserProvider')
}
return context
}
// 4. ✅ Usage in components (MAGIC!)
function AppWithContext() {
return (
<UserProvider>
<div>
<Layout>
<Dashboard />
</Layout>
</div>
</UserProvider>
)
}
// No more props needed! 🎉
function Layout({ children }) {
const { user } = useUser()
return (
<div style={{
backgroundColor: user.theme === 'dark' ? '#333' : '#fff',
color: user.theme === 'dark' ? '#fff' : '#333',
minHeight: '100vh'
}}>
<Header />
<main style={{ padding: '20px' }}>
{children}
</main>
</div>
)
}
function Header() {
return (
<header style={{ padding: '20px', borderBottom: '1px solid #ccc' }}>
<UserInfo />
</header>
)
}
function UserInfo() {
const { user, toggleTheme } = useUser()
return (
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<div>
<h2>Hello {user.name} !</h2>
<p>Theme: {user.theme}</p>
</div>
<button onClick={toggleTheme}>
{user.theme === 'light' ? '🌙 Dark mode' : '☀️ Light mode'}
</button>
</div>
)
}
function Dashboard() {
return (
<div>
<h3>Dashboard</h3>
<div style={{ display: 'flex', gap: '20px' }}>
<Profile />
<Settings />
</div>
</div>
)
}
function Profile() {
const { user, updateUser } = useUser()
const [isEditing, setIsEditing] = useState(false)
const [tempName, setTempName] = useState(user.name)
const handleSave = () => {
updateUser({ name: tempName })
setIsEditing(false)
}
const handleCancel = () => {
setTempName(user.name)
setIsEditing(false)
}
return (
<div style={{ border: '1px solid #ccc', padding: '20px', borderRadius: '8px' }}>
<h4>Profile</h4>
{isEditing ? (
<div>
<input
value={tempName}
onChange={e => setTempName(e.target.value)}
/>
<div>
<button onClick={handleSave}>✅ Save</button>
<button onClick={handleCancel}>❌ Cancel</button>
</div>
</div>
) : (
<div>
<p><strong>Name:</strong> {user.name}</p>
<p><strong>Email:</strong> {user.email}</p>
<button onClick={() => setIsEditing(true)}>✏️ Edit</button>
</div>
)}
</div>
)
}
function Settings() {
const { user, updatePreferences } = useUser()
return (
<div style={{ border: '1px solid #ccc', padding: '20px', borderRadius: '8px' }}>
<h4>Settings</h4>
<div>
<label>
<input
type="checkbox"
checked={user.preferences.notifications}
onChange={e => updatePreferences({ notifications: e.target.checked })}
/>
Notifications
</label>
</div>
<div>
<label>
Language:
<select
value={user.preferences.language}
onChange={e => updatePreferences({ language: e.target.value })}
>
<option value="fr">Français</option>
<option value="en">English</option>
<option value="es">Español</option>
</select>
</label>
</div>
</div>
)
}