React Architecture Avancée

Custom Hooks, Error Boundaries & Architecture par Feature

Projet de synthèse — Semaine 4

Objectifs de la leçon

1. Créer des custom hooks

Extraire la logique réutilisable

2. Implémenter les Error Boundaries

Gérer les crashs de composants

3. Organiser par feature

Architecture maintenable

4. Réviser semaines 3-4

Consolider les acquis

5. Appliquer les bonnes pratiques

Architecture React maintenable

Plan du cours

1

Revue semaine 4

useEffect, useRef, React Router, Context, useReducer

2

Custom Hooks

Extraire la logique réutilisable

3

Error Boundaries

Gérer les crashs de composants

4

Architecture par Feature

Organisation et conventions de nommage

5

Mini-projet

Construire une app complète

Revue Semaine 4

Ce que nous avons appris

useEffect

Effets de bord

useRef

DOM & valeurs mutables

React Router

Navigation SPA

Context + useReducer

State global

useEffect — Rappel

useEffect(() => {

// Code exécuté après le rendu

// Ex: fetch, subscriptions, timers

return () => {

// Cleanup function

};

}, [dependencies]);

[]

1x au montage

[dep]

Ă€ chaque changement

pas de []

Ă€ chaque rendu

useRef — Rappel

Accès DOM

const inputRef = useRef();

<input ref={inputRef} />

// Focus programmatique

inputRef.current.focus();

Valeur mutable

const countRef = useRef(0);

// Ne déclenche pas de re-render

countRef.current++;

đź’ˇ useRef = valeur persistante sans re-render

React Router — Rappel

<BrowserRouter>

<Routes>

<Route path="/" element={<Home />} />

<Route path="/about" element={<About />} />

<Route path="/users/:id" element={<User />} />

</Routes>

</BrowserRouter>

useNavigate()

Navigation programmatique

useParams()

Accès aux paramètres d'URL

Context + useReducer — Rappel

// 1. Créer le Context

const AppContext = createContext();

// 2. Provider avec useReducer

const [state, dispatch] = useReducer(reducer, initialState);

// 3. Consommer dans les composants

const { state, dispatch } = useContext(AppContext);

useReducer

State complexe avec actions

Context

Partager sans prop drilling

Custom Hooks

Extraire la logique réutilisable

Une fonction qui commence par use

et utilise d'autres hooks à l'intérieur

La règle d'or

Un custom hook = une fonction qui :

1

Commence par use

2

Utilise d'autres hooks (useState, useEffect, etc.)

3

Retourne des valeurs ou fonctions

💡 Les autres hooks peuvent être utilisés uniquement à l'intérieur des custom hooks ou composants

Exemple : useToggle

Hook simple pour basculer un état booléen

function useToggle(initialValue = false) {

const [value, setValue] = useState(initialValue);

const toggle = () => setValue(v => !v);

return [value, toggle];

}

// Utilisation

const [isOpen, toggleOpen] = useToggle();

<button onClick={toggleOpen}>

{isOpen ? 'Fermer' : 'Ouvrir'}

</button>

Exemple : useFetch

Hook pour les appels API avec loading et error

function useFetch<T>(url: string) {

const [data, setData] = useState<T | null>(null);

const [loading, setLoading] = useState(true);

const [error, setError] = useState<string | null>(null);

useEffect(() => {

fetch(url)

.then(res => res.json())

.then(setData)

.catch(() => setError('Erreur de chargement'))

.finally(() => setLoading(false));

}, [url]);

return { data, loading, error };

}

💡 Réutilisable dans tous les composants !

Utilisation de useFetch

function UserList() {

const { data, loading, error } = useFetch<User[]>(

'https://api.example.com/users'

);

if (loading) return <p>Chargement...</p>;

if (error) return <p>{error}</p>;

return (

<ul>

{data?.map(user => (

<li key={user.id}>{user.name}</li>

))}

</ul>

);

}

✅ Logique de fetch extraite — composant plus lisible

Exemple : useLocalStorage

Hook pour synchroniser state avec localStorage

function useLocalStorage<T>(key: string, initialValue: T) {

const [value, setValue] = useState<T>(() => {

const stored = localStorage.getItem(key);

return stored ? JSON.parse(stored) : initialValue;

});

useEffect(() => {

localStorage.setItem(key, JSON.stringify(value));

}, [key, value]);

return [value, setValue] as const;

}

💡 Persiste automatiquement les données

Quand extraire un hook ?

❌ Trop tôt

  • • Logique utilisĂ©e 1 seule fois
  • • Abstraction prĂ©maturĂ©e
  • • Hook trop spĂ©cifique

âś… Bon moment

  • • Logique rĂ©utilisĂ©e 2+ fois
  • • ComplexitĂ© isolable
  • • API claire et simple

🎯 Trouver le bon niveau d'abstraction — ni trop générique, ni trop spécifique

Error Boundaries

Le try/catch de React

Attrape les erreurs pendant le rendu

EmpĂŞche toute l'app de planter

Error Boundary = Composant Class

⚠️ Les Error Boundaries DOIVENT être des composants class

class ErrorBoundary extends React.Component {

state = { hasError: false };

static getDerivedStateFromError(error: Error) {

return { hasError: true };

}

render() {

if (this.state.hasError) {

return <h1>Une erreur est survenue.</h1>;

}

return this.props.children;

}

}

Utilisation

<ErrorBoundary>

<MyComponent />

</ErrorBoundary>

âś… Si MyComponent plante, l'UI de fallback s'affiche

Le reste de l'application continue de fonctionner

// Placement recommandé : au niveau de la route

<Route path="/dashboard" element={

<ErrorBoundary>

<Dashboard />

</ErrorBoundary>

} />

Ce que les Error Boundaries N'attrapent PAS

⚠️ Ils n'attrapent que les erreurs pendant le rendu

❌ N'attrapent PAS

  • • Erreurs dans les event handlers
  • • Erreurs dans setTimeout
  • • Erreurs dans les effets (useEffect)
  • • Erreurs cĂ´tĂ© serveur (SSR)

âś… Attrapent

  • • Erreurs pendant render()
  • • Erreurs dans le constructor
  • • Erreurs dans getDerivedStateFromError

đź’ˇ Pour les event handlers, utilisez try/catch classique

Architecture par Feature

Organiser pour la maintenabilité

Chaque feature = son propre dossier

Avec ses composants, hooks, types, context...

Structure de dossiers

❌ Par type

src/

components/

Header.jsx

UserCard.jsx

ProductList.jsx

hooks/

useUser.js

useProduct.js

Difficile de naviguer

âś… Par feature

src/

features/

user/

UserCard.jsx

useUser.js

userTypes.ts

product/

ProductList.jsx

useProduct.js

Tout est ensemble

Contenu d'une feature

features/

auth/

components/

LoginForm.jsx

RegisterForm.jsx

hooks/

useAuth.js

context/

AuthContext.jsx

types/

authTypes.ts

api/

authApi.js

index.js

// exports publics

đź’ˇ index.js exporte l'API publique de la feature

Conventions de nommage

Composants
PascalCase: UserCard.jsx
Hooks
camelCase + use: useUser.js
Context
PascalCase + Context: AuthContext.jsx
Types
camelCase + Types: authTypes.ts

Pièges courants à éviter

❌ Hooks trop génériques ou trop spécifiques

âś… Trouver le bon niveau d'abstraction

❌ Oublier que les Error Boundaries n'attrapent pas les erreurs dans les event handlers

âś… Utiliser try/catch dans les onClick, onSubmit, etc.

❌ Toute la logique dans les composants

✅ Extraire dans des custom hooks pour la réutilisabilité

❌ Ne pas typer les retours des custom hooks

âś… Utiliser TypeScript pour une meilleure DX

Points clés à retenir

Custom Hook = fonction qui commence par 'use'

Extrait la logique pour la rendre réutilisable

Error Boundary

Le try/catch de React

Composant class obligatoire

Feature Folders

Chaque feature = son dossier

Composants, hooks, types ensemble

⚠️ Error Boundaries n'attrapent PAS les erreurs dans les event handlers

Ă€ retenir !

Custom Hooks

Extraire la logique

→ Composants plus lisibles

Error Boundaries

Attraper les crashs

→ UI de fallback

Architecture

Organiser par feature

→ Code maintenable

🎯 Prêt pour le mini-projet !

Construire une app React complète

Questions?

Architecture React avancée

Mini-projet : Appliquer tous les concepts

Custom hooks + Error Boundaries + Feature folders