React - useContext : Partage de Données
Quand tu dois passer des données à travers 5-6 composants, useContext va te sauver la vie. C'est le partage de données sans se prendre la tête !
-> on peut s'en servir de store, attention cependant, tout ce qui est à l'interieur est re-render, au premier changement.
donc à utilisé avec parcimonie
Le Problème : Prop Drilling
L'Enfer du Prop Drilling
// ❌ CAUCHEMAR - Prop drilling sur 6 niveaux !
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>Salut {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 passées partout !
// 🐛 Difficile à maintenir !
// 💀 Composants intermédiaires pollués !
La Solution : useContext
Créer un Context
import { createContext, useContext, useState } from 'react'
// 1. ✅ Créer le 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'
}
})
// Fonctions pour modifier l'utilisateur
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'
}))
}, [])
// Mémoriser la valeur pour éviter les re-renders
const contextValue = useMemo(() => ({
user,
updateUser,
updatePreferences,
toggleTheme
}), [user, updateUser, updatePreferences, toggleTheme])
return (
<UserContext.Provider value={contextValue}>
{children}
</UserContext.Provider>
)
}
// 3. ✅ Hook custom pour utiliser le Context
function useUser() {
const context = useContext(UserContext)
if (!context) {
throw new Error('useUser must be used within a UserProvider')
}
return context
}
// 4. ✅ Usage dans les composants (MAGIC !)
function AppWithContext() {
return (
<UserProvider>
<div>
<Layout>
<Dashboard />
</Layout>
</div>
</UserProvider>
)
}
// Plus besoin de props ! 🎉
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>Salut {user.name} !</h2>
<p>Thème: {user.theme}</p>
</div>
<button onClick={toggleTheme}>
{user.theme === 'light' ? '🌙 Mode sombre' : '☀️ Mode clair'}
</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>Profil</h4>
{isEditing ? (
<div>
<input
value={tempName}
onChange={e => setTempName(e.target.value)}
/>
<div>
<button onClick={handleSave}>✅ Sauvegarder</button>
<button onClick={handleCancel}>❌ Annuler</button>
</div>
</div>
) : (
<div>
<p><strong>Nom:</strong> {user.name}</p>
<p><strong>Email:</strong> {user.email}</p>
<button onClick={() => setIsEditing(true)}>✏️ Modifier</button>
</div>
)}
</div>
)
}
function Settings() {
const { user, updatePreferences } = useUser()
return (
<div style={{ border: '1px solid #ccc', padding: '20px', borderRadius: '8px' }}>
<h4>Paramètres</h4>
<div>
<label>
<input
type="checkbox"
checked={user.preferences.notifications}
onChange={e => updatePreferences({ notifications: e.target.checked })}
/>
Notifications
</label>
</div>
<div>
<label>
Langue:
<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>
)
}