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>
  )
}

Ressources Pour Aller Plus Loin