Toutes les formules
Le fichier de révision. Chaque module des guides isole un patron de code canonique — les voici sortis de leurs chapitres, réunis. Cette page n'apprend rien de neuf : elle sert à recroiser, encore et encore, jusqu'à ce que reconnaître devienne un réflexe.
Ne la lis pas d'un bout à l'autre comme un module. Ouvre-la quand tu as dix minutes, descends au hasard, et devant chaque formule pose-toi une seule question : « est-ce que je saurais dire à quoi elle sert et où elle s'emploie ? » Si oui, tu passes. Sinon, le lien à droite te ramène au module qui l'explique.
La reconnaissance vient de la fréquence des rencontres, pas de la durée de chacune. Cinq passages de dix minutes valent mieux qu'une relecture d'une heure.
Lis les noms à la suite et une famille apparaît. Le fetch honnête vérifie
res.ok au lieu de faire confiance. Le handler POST honnête
revalide côté serveur ce que le client a déjà validé. Le repli qui ne ment pas
admet qu'aucune configuration ne correspond. Le validateur pur rend un verdict
sans effet de bord caché. Le test d'intégration qui parle échoue vraiment
quand la fonctionnalité casse, au lieu de passer au vert sur du néant.
Cinq formules, une seule idée : un bon code admet ce qu'il ne sait pas. Il ne suppose pas que la requête a réussi, que la donnée est valide, que la clé existe, que le test a vérifié quelque chose. C'est la ligne de partage la plus fiable entre du code de tutoriel et du code de production — et c'est aussi, en entrevue, ce qui distingue une réponse récitée d'une réponse vécue.
Le Web moderne & Next.js
9 formulesDu squelette d'une page jusqu'à la présentation d'un test technique à l'oral.
<!DOCTYPE html> <!-- "ceci est du HTML moderne" -->
<html lang="fr"> <!-- racine du document -->
<head> <!-- métadonnées : RIEN d'affiché ici -->
<meta charset="UTF-8"> <!-- encodage des caractères (accents !) -->
<title>RendezVous</title> <!-- le titre de l'onglet -->
</head>
<body> <!-- tout ce qui est VISIBLE vit ici -->
<h1>Prendre rendez-vous</h1>
<script src="app.js" defer></script> <!-- le JS, chargé sans bloquer -->
</body>
</html>
Deux zones, une règle : le <head> décrit la page (invisible), le <body> la contient (visible). Tu la reconnaîtras partout — y compris dans ce que Next.js envoie au navigateur, et jusque dans les pages de ce guide, qui sont elles-mêmes bâties sur ce squelette.
element.addEventListener('click', (event) => {
event.preventDefault(); // si l'élément a un comportement par défaut
// ... réagir : lire des valeurs, modifier le DOM, appeler le réseau
});
C'est le patron le plus répété de tout le frontend : tu le verras tel quel en vanilla JS, dans les DevTools, dans les vieux tutoriels — et déguisé en onClick={...} ou onSubmit={...} en React, où c'est la bibliothèque qui pose l'écouteur pour toi (on le re-démontera dans le module « React web ↔ React Native : les ponts »). Chaque fois que tu vois un nom d'événement et un callback, c'est cette formule.
<label htmlFor="email">Courriel</label>
<input
id="email" // le même identifiant des deux côtés : c'est LE lien
type="email" // bon clavier mobile + validation native
aria-describedby="email-err" // « ma description est là-bas » (l'erreur)
aria-invalid={hasError} // état invalide annoncé au lecteur d'écran
/>
<p id="email-err" role="alert">Adresse email invalide.</p>
On l'emploie pour chaque champ de formulaire, sans exception ; on la reconnaît au trio htmlFor/id identiques (en HTML pur : for="email" — React impose htmlFor). Le role="alert" de la dernière ligne, lui, veut dire « lis-moi à voix haute dès que j'apparais » — on le détaille à la section sur ARIA. Tu reverras cette formule telle quelle au module « Formulaires 2 — validation & erreurs », où l'on branchera hasError sur une vraie validation.
.row {
display: flex; /* passe les enfants en flexbox */
align-items: center; /* alignés verticalement au centre */
justify-content: space-between; /* premiers/derniers aux bords, */
gap: 12px; /* espace régulier entre les enfants */
}
C'est le patron le plus tapé du frontend web — barres d'en-tête, lignes de liste, pieds de carte : un coup d'œil et tu le reconnais partout. En React Native il s'écrit exactement pareil, à un détail près : il faut ajouter flexDirection: 'row', puisque le défaut y est column.
.services {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
gap: 16px;
}
La grille responsive sans une seule media query : « autant de colonnes d'au moins 240 px que la place le permet, qui se partagent le reste ». Apprends à la reconnaître d'un coup d'œil — elle est partout, des portfolios aux dashboards, et c'est souvent la première chose qu'on écrit dans un test technique de mise en page.
const res = await fetch('/api/services');
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const services = await res.json();
Les trois lignes que tout frontend tape chaque semaine : demander, vérifier le verdict, lire le corps. La deuxième ligne est celle que les juniors oublient — sans elle, un 500 passe pour un succès et le crash arrive plus loin, méconnaissable.
import { createRoot } from 'react-dom/client';
import App from './App';
// 1. On trouve la boîte vide définie dans index.html
// 2. createRoot en fait un « territoire » géré par React
// 3. render y peint l'arbre décrit par <App />
createRoot(document.getElementById('root')).render(<App />);
C'est tout ce que « monter React » veut dire : désigner un nœud du DOM et dire à React « à partir d'ici, c'est toi qui commandes ». Retiens-la quand même, même si Next.js te la cachera entièrement dès le module « La carte de Next.js » — c'est exactement ce qui te permettra de comprendre ce que Next.js fait à ta place.
const [values, setValues] = useState({});
const handleChange = (e) =>
setValues({ ...values, [e.target.name]: e.target.value });
Un seul handleChange dessert N champs : on le branche tel quel sur chaque onChange, et c'est l'attribut name du champ qui dit quelle clé modifier. On la reverra enrichie dans le module Formulaires 2 — validation & erreurs, où le même handler effacera aussi l'erreur du champ touché.
function validate(values) {
const errors = {};
if (!values.fullName?.trim()) errors.fullName = 'Le nom est requis.';
if (!/.+@.+\..+/.test(values.email ?? '')) errors.email = 'Courriel invalide.';
return errors; // {} = valide
}
Une fonction pure, testable, totalement indépendante de React : on lui donne les valeurs, elle rend un dictionnaire { field: message }, et un objet vide signifie « tout va bien ». C'est le patron qu'attendent les évaluateurs quand ils te demandent « ajoute la validation » : ils regardent si les règles vivent dans un seul endroit isolé, ou si elles sont éparpillées dans le JSX.
Next.js en profondeur
6 formulesApp Router · RSC · rendu · mutations
// app/layout.tsx — OBLIGATOIRE, une seule fois, à la racine.
// C'est lui qui écrit <html> et <body> : il n'y a pas d'index.html.
export default function RootLayout({
children, // ← le segment enfant : la page, ou un layout plus profond
}: { children: React.ReactNode }) {
return (
<html lang="fr">
<body>{children}</body>
</html>
);
}
// app/page.tsx — la page de l'URL « / ». Export DEFAULT obligatoire.
export default function Page() {
return <h1>Bienvenue chez RendezVous</h1>;
}
Le layout enveloppe, la page occupe. Chaque segment de l'App Router est une variation de ce duo : un layout optionnel qui persiste, une page qui change. Tu la reconnaîtras à ses deux marques : l'export default et le paramètre children du layout.
'use client';
import { useState } from 'react';
export default function AppointmentForm({ services }) {
const [service, setService] = useState('');
// … interactivité ici
}
La directive marque le début du monde navigateur : à partir de cette ligne, on a le droit d'utiliser l'état, les événements et les API du navigateur. Et tout ce que ce fichier importe le rejoint automatiquement dans le bundle client — d'où la règle : place-la le plus bas possible dans l'arbre.
import Link from 'next/link';
// L'utilisateur clique → toujours un Link (préchargé, accessible, vrai <a>)
<Link href={`/rendez-vous/${service.slug}`}>{service.nom}</Link>
// Et côté client, quand la navigation suit une action :
'use client';
import { useRouter } from 'next/navigation';
const router = useRouter();
router.push('/confirmation'); // push = on empile ; replace = pas de retour
Link pour tout ce que l'utilisateur clique : il précharge la destination, garde le comportement natif d'un lien (nouvel onglet, clavier, lecteur d'écran) et évite le rechargement complet. useRouter() pour les navigations déclenchées par du code — après un formulaire, un paiement, un changement de filtre — et jamais l'inverse : un lien déguisé en bouton, c'est un lien qu'on ne peut ni ouvrir dans un onglet, ni atteindre au clavier.
export default async function Page() {
const res = await fetch('https://api.exemple.ca/services');
if (!res.ok) throw new Error('Chargement impossible');
const services = await res.json();
return <ListeServices services={services} />;
}
Pas de useEffect, pas de useState, pas de spinner : la page n'existe qu'une fois les données là. Ce qui montre l'attente à l'utilisateur, c'est loading.js ou un <Suspense> (section « Le streaming : envoyer la page en morceaux ») — pas un état à toi.
export async function POST(request: Request) {
const body = await request.json();
const errors = validate(body);
if (Object.keys(errors).length) return Response.json({ errors }, { status: 400 });
const created = await creerRendezVous(body);
return Response.json(created, { status: 201 });
}
Lire, valider, agir, répondre avec le bon statut — les quatre gestes, toujours dans cet ordre. Le jour où tu vois un handler qui agit avant de valider, ou qui répond 200 à une création, tu tiens le bug avant même d'avoir lu le reste du fichier.
it('envoie les values saisies quand le formulaire est soumis', async () => {
render(<PageRendezVous />);
await userEvent.selectOptions(screen.getByLabelText('Service'), 'Échographie');
await userEvent.type(screen.getByLabelText('Courriel'), 'a@b.ca');
await userEvent.click(screen.getByRole('button', { name: /réserver/i }));
expect(await screen.findByText(/rendez-vous confirmé/i)).toBeInTheDocument();
});
Relis ces six lignes et cherche un détail d'implémentation : il n'y en a aucun. Pas un nom d'état, pas une classe CSS, pas un nom de fonction interne — seulement des étiquettes, un libellé de bouton, et un message visible. Ce test survivrait à une réécriture complète du composant : passage de useState à useReducer, découpage en cinq sous-composants, migration vers React Hook Form ou vers une Server Action. Tant que la clinique se réserve de la même façon, il reste vert.
Le test technique
5 formulesLire, critiquer, présenter — et l'entrevue
Observation : « Ici, la validation n'existe que côté client. »
Impact : « Une requête envoyée directement à l'API créerait un
rendez-vous invalide. »
Proposition : « Est-ce qu'on pourrait rejouer le validateur dans le
Route Handler ? »
Le même patron sert en revue de code, en entrevue et pour se relire soi-même : un fait vérifiable, sa conséquence concrète, puis une piste posée en question plutôt qu'en verdict. Il décrit un code, jamais une personne — c'est précisément ce qui le rend recevable.
function findFormConfig(configs, service) {
return configs.find((c) => c.services.includes(service))
?? configs.find((c) => c.services.includes('*'));
}
La correspondance exacte d'abord, le joker ensuite : la priorité est écrite dans la structure du code, pas dans l'ordre des données. Et le type de retour admet l'absence (les deux recherches peuvent échouer), ce qui oblige l'appelant à traiter ce cas — c'est précisément ce qui empêche un plantage à l'exécution le jour où un service n'a ni configuration dédiée ni repli.
// UN SEUL endroit du projet traduit le vocabulaire des données
// vers celui du HTML. Partout ailleurs, on parle « config ».
{field.type === 'dropdown' ? (
<select {...common}>…</select>
) : field.type === 'phone' ? (
<input {...common} type="tel" /> // "phone" ici, "tel" en HTML
) : (
<input {...common} type="text" /> // le repli, jamais absent
)}
Deux vocabulaires se rencontrent dans tout projet piloté par des données : celui de la configuration et celui de la plateforme. La formule consiste à les faire se rencontrer en un seul point, et à écrire partout ailleurs le vocabulaire des données. Quand un nouveau type de champ apparaît, tu sais exactement quel fichier ouvrir ; et l'objet common, étalé dans chaque branche, garantit que le trio d'accessibilité s'applique à tous les contrôles sans qu'on ait à y penser.
1. Le problème — « On devait construire X, avec les contraintes Y. »
2. L'architecture — « J'ai découpé en A, B, C ; l'état vit dans A parce que… »
3. Une trace — « Voici ce qui se passe quand l'utilisateur clique ici,
jusqu'au serveur. »
4. Un choix — « J'ai pris X plutôt que Y parce que… »
5. La suite — « Avec une journée de plus : validation serveur, tests,
accessibilité. »
Le même plan tient en cinq minutes comme en trente : chaque temps se dilate ou se comprime sans que la structure change. Et il évite le récit ligne par ligne, qui perd tout le monde — y compris toi.
Devant un outil, une bibliothèque, un framework :
« Qu'est-ce que cet outil m'apporte
que le niveau en dessous ne m'apportait pas ? »
→ Réponse en une phrase concrète : l'outil est justifié.
→ « rien, mais c'est plus moderne » : le niveau en dessous suffisait.
La question qui gouverne toute cette carte, et tu la verras revenir à chaque territoire : on ne quitte le CSS pour Motion, Motion pour GSAP, le HTML pour la 3D, les props pour un magasin global que lorsqu'on peut nommer ce que l'étage supérieur apporte de plus. Elle sert autant en entrevue qu'en revue de code, et t'évite le piège numéro un du développeur enthousiaste : adopter un outil avant d'avoir le problème.
IFT-2007 · Analyse OO
7 formulesProcessus unifié · UML · patrons de conception
1. LA FRONTIÈRE — qu'est-ce qui est DANS le système, et qu'est-ce qui est dehors ?
2. LES ÉLÉMENTS — que représentent les boîtes ? des concepts ? des objets ? des acteurs ?
3. LES RELATIONS — que veulent dire les traits, et surtout DANS QUEL SENS se lisent-ils ?
La manière de lire n'importe quel diagramme UML, dans cet ordre. On l'emploie devant un diagramme inconnu ; on la reconnaît au fait que la troisième question est toujours la plus payante — c'est le sens des traits qui porte le sens du modèle, et c'est aussi ce qu'on rate le plus souvent.
1. SOULIGNER tous les noms communs de l'énoncé, sans juger.
2. TRIER chacun en trois tas :
· CONCEPT — il a une identité propre, on peut en avoir plusieurs
et les distinguer → une boîte
· ATTRIBUT — c'est une valeur simple qui appartient à autre chose
→ une ligne dans une boîte
· BRUIT — synonyme, mot du système, ou concept hors sujet
→ on le raye
3. RELIER une association par FAIT du métier, nommée par un VERBE
(« emprunte », « contient », « réfère »), avec son sens de lecture.
4. COMPTER la multiplicité à chaque bout, en posant à chaque fois
la même question : « combien de X pour UN SEUL Y ? »
La méthode de construction d'un modèle du domaine. On l'emploie sur toute description textuelle ; on la reconnaît au fait que l'étape 2 est la seule qui demande de réfléchir — les trois autres sont mécaniques.
Nom : un VERBE à l'infinitif + son complément
Acteur principal : qui déclenche, et pour quel objectif
Préconditions : ce qui est VRAI avant qu'on commence
Garanties (succès) : ce qui sera VRAI à la fin, si tout va bien
Scénario principal : les étapes numérotées, quand TOUT va bien
Extensions : 3a, 5a… — les variantes, rattachées à leur étape
Le format de Larman, celui que le cours emploie. On l'écrit pour chaque cas d'utilisation retenu ; on le reconnaît au fait que les deux dernières lignes pèsent plus que les quatre premières réunies.
VISIBILITÉ + public accessible de partout
- privé accessible dans la classe seule
# protégé la classe et ses sous-classes
~ paquetage les classes du même paquetage
ATTRIBUT visibilité nom : Type [multiplicité] = valeurParDefaut
- solde : double = 0.0
- lignes : LigneCommande [1..*]
OPÉRATION visibilité nom(param : Type, ...) : TypeRetour
+ calculerTotal() : double
+ ajouterLigne(plat : Plat, quantite : int) : void
souligné = membre de CLASSE italique = ABSTRAIT
La notation d'un membre de classe en UML. On l'écrit pour chaque ligne des deux compartiments du bas ; on la reconnaît au fait que tout y est facultatif sauf le nom et la visibilité — et que c'est la visibilité qu'on oublie.
LIGNE DE VIE
:Livre un objet de Livre
emprunte:Livre un objet nommé
Livre la CLASSE, pas un objet
Usager un acteur, sans boîte
sous la boîte, un trait POINTILLÉ :
c'est le TEMPS. Rien n'est à l'échelle,
seul l'ORDRE compte.
BARRE D'ACTIVATION
posée sur le trait = l'objet EXÉCUTE
début : la réception ; fin : la réponse
MESSAGE
[garde] valeur := nom(arg : Type)
estDisponible()
[en regle] no := emprunter(livre)
RETOUR
flèche pointillée, portant la VALEUR
La grammaire d'un diagramme de séquence, en entier. On la reconnaît à ce que tout y est facultatif sauf le nom du message : la garde, la valeur de retour et même les arguments s'omettent dès qu'ils vont de soi.
<responsabilité> → <classe>
par <PATRON>, parce que <raison>
calculerSousTotal() → LigneCommande
par EXPERT, parce qu'elle detient la
quantite et atteint le prix du plat
creerEmprunt() → Lecteur
par CREATEUR, parce qu'il enregistre
ses emprunts
La raison nomme un FAIT du diagramme : une
donnee possedee, une agregation, un
evenement recu. Jamais « c'est plus propre ».
Le gabarit de toute justification GRASP, et la forme exacte attendue sur une copie. On le reconnaît à ce qu'il tient en une ligne et demie : si la raison prend trois phrases, c'est que le patron invoqué n'est pas le bon.
événement [garde] / action
ÉVÉNEMENT ce qui ARRIVE à l'objet
rendre() un appel
après(30 jours) le temps
quand(solde < 0) une condition
devenue vraie
[GARDE] une condition BOOLÉENNE, testée
quand l'événement arrive.
Fausse : rien ne se passe.
[prolongations < 1]
/ ACTION ce qui S'EXÉCUTE pendant le
passage. Instantané.
/ facturerAmende()
ÉTIQUETTES COMPLÈTES
rendre()
rendre() / facturerAmende()
prolonger(j) [prolongations < 1]
payer(m) [solde >= m] / debiter(m)
Seul l'événement est normalement présent ; la garde et l'action s'omettent dès qu'il n'y en a pas. C'est la réponse à la question la plus fréquente sur cette notation, et la source des étiquettes qu'on croit incomplètes alors qu'elles sont justes. Une flèche nue, sans aucun événement, existe aussi — c'est la transition automatique, traitée plus bas —, mais c'est un cas particulier qu'on n'écrit que lorsqu'on le veut vraiment.
IFT-2008 · Algorithmes
5 formulesAnalyse · structures de données · C++
// GESTE 1 — le baromètre : l'instruction la plus interne.
// GESTE 2 — le compte : pour i fixé, l'interne tourne i fois.
// Donc le total s'écrit somme de i, pour i de 1 à n.
// GESTE 3 — le formulaire : cette somme vaut n(n+1)/2.
// GESTE 4 — la conclusion : n(n+1)/2 est dominé par n², donc Theta(n^2).
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= i; j++) {
somme += j; // ← le baromètre
}
}
On l'emploie sur toute boucle imbriquée ; on la reconnaît au fait que la borne de la boucle interne dépend de l'indice externe, ce qui donne toujours une somme et non un produit.
template<typename T> // 1. générique : un moule, tous les types
class Conteneur {
private:
T* donnees; // 2. ce qu'on POSSÈDE, et qu'il faudra rendre
int taille;
public:
Conteneur(int n) // 3. liste d'initialisation, PUIS allocation
: taille(n) { donnees = new T[n]; }
~Conteneur() { delete[] donnees; } // 4. le delete vit ICI, toujours
void ajouter(const T& v); // 5. on reçoit en const T&
int getTaille() const; // 6. qui ne modifie pas est const
};
Le squelette de toutes les structures du cours : liste, pile, file, arbre, monceau. On la reconnaît à la paire new dans le constructeur / delete dans le destructeur — et dès qu'on la voit, on sait qu'il faut chercher les trois méthodes de la règle des trois.
// Visiter tous les nœuds, du premier au dernier.
for (Noeud* courant = tete; courant != nullptr; courant = courant->suivant) {
// ... traiter courant->valeur
}
// La VARIANTE de suppression : on s'arrête sur le nœud d'AVANT,
// parce qu'une liste simple ne sait pas revenir en arrière.
Noeud* courant = tete;
while (courant->suivant != nullptr && courant->suivant->valeur != v)
courant = courant->suivant;
// ici, courant->suivant est le nœud à retirer (ou nullptr : absent)
Le patron le plus réutilisé du reste du cours. On le reconnaît à sa condition d'arrêt — != nullptr, jamais un indice — et à sa mise à jour, qui suit un pointeur au lieu d'incrémenter un compteur. Chaque fois qu'une opération demande de trouver quelque chose dans une liste, c'est cette boucle qui coûte le Θ(n).
marquer(depart);
attente.ajouter(depart);
while (!attente.vide()) {
u = attente.retirer(); // <-- LA SEULE LIGNE QUI DIFFÈRE
traiter(u);
for (int v : voisins[u]) {
if (!marque(v)) {
marquer(v);
attente.ajouter(v);
}
}
}
// attente = file -> parcours en LARGEUR
// attente = pile -> parcours en PROFONDEUR
Le patron le plus rentable du chapitre, parce qu'il n'y en a qu'un. BFS et DFS ne sont pas deux algorithmes : c'est un algorithme, paramétré par la nature de la structure d'attente. Retiens-le sous cette forme et tu n'auras plus jamais deux codes à mémoriser — seulement la question « qui sort en premier ? ». Le module suivant le rappellera trois fois : la détection de cycle, le tri topologique et les composantes fortement connexes sont ce même squelette, avec un geste de plus au moment de traiter.
marquer(depart);
attente.ajouter(depart);
while (!attente.vide()) {
u = attente.retirer(); // file -> largeur ; pile -> profondeur
traiter(u); // <-- TOUT CE MODULE TIENT ICI
for (int v : voisins[u]) {
if (!marque(v)) { marquer(v); attente.ajouter(v); }
}
}
La ligne traiter(u) était un affichage dans le module précédent. Remplace-la par « noter que u est en cours d'exploration » et tu détectes un cycle ; par « empiler u en sortant » et tu obtiens un tri topologique ; par « ajouter u au groupe courant » et tu comptes des composantes. Un seul squelette, quatre algorithmes.
Lire Halterofit
13 formulesLes patrons qu'on recroise partout dans un projet React Native / Expo / TypeScript.
export function getEmailError(email: string): string | null {
const trimmed = email.trim();
if (trimmed.length === 0) return 'Email is required';
if (trimmed.length > MAX_EMAIL_LENGTH) return 'Email cannot exceed…';
if (!EMAIL_REGEX.test(trimmed)) return 'Enter a valid email address';
return null; // aucune garde n'a mordu → tout va bien
}
Quand on valide une donnée venue de l'extérieur avant de s'en servir. On la reconnaît à sa pile de if qui sortent tôt, sans else, et au cas normal seul tout en bas (null = pas d'erreur).
const handleSignUp = async () => {
setError('');
setIsLoading(true);
try {
await signUp(email, password, displayName); // pause NON bloquante
router.replace({ pathname: '/(auth)/confirm-account' });
} catch (err) {
setError(isOperationalError(err) ? err.userMessage : 'Something went wrong.');
} finally {
setIsLoading(false); // succès OU échec : on éteint le sablier
}
};
Quand un geste de l'utilisateur déclenche une opération longue (réseau, base). On la reconnaît au trio setIsLoading(true) → try/catch → finally qui remet le drapeau à false quoi qu'il arrive.
// Pour CRÉER : la base génère l'id → on le retire du type d'entrée.
export type CreateWorkout = Omit<Workout, 'id'>;
// Pour METTRE À JOUR : on n'envoie que ce qui change → tout devient facultatif.
export type UpdateWorkout = Partial<Omit<Workout, 'id'>>;
Quand une entité a plusieurs formes selon le moment (créer, modifier, afficher). On la reconnaît aux chevrons imbriqués, qui se lisent de l'intérieur vers l'extérieur : le noyau, puis les couches.
// Le parent possède la donnée et la fait DESCENDRE en prop ;
// il passe un callback pour que l'enfant fasse REMONTER l'événement.
{series.map((s) => (
<SetRow
key={s.id} // identité stable, pour le diff de React
reps={s.reps} // donnée qui descend (prop, lecture seule)
onComplete={() => marquerFait()} // événement qui remonte (callback)
/>
))}
Dès qu'un composant en rend un autre. On la reconnaît au couple « une prop de données + une prop on… », et à la key dès qu'il y a un .map().
useEffect(() => {
if (startEpochMs == null) return; // rien à synchroniser
const tick = () =>
setElapsed(Math.max(0, Math.floor((Date.now() - startEpochMs) / 1000)));
const timeout = setTimeout(tick, 0);
const interval = setInterval(tick, 1000);
return () => { // le nettoyage débranche ce que l'effet a branché
clearTimeout(timeout);
clearInterval(interval);
};
}, [startEpochMs]); // quand faut-il rejouer ?
Quand le composant doit se synchroniser avec un système extérieur (minuteur, abonnement, écouteur). On la reconnaît à ses trois parties : l'effet, la fonction de nettoyage retournée, et le tableau de dépendances.
const exercises = useMemo(() => {
const dbExercises = workout?.exercises ?? [];
if (dbExercises.length === 0) return dbExercises;
const visibleIds = new Set(exerciseIdsKey ? exerciseIdsKey.split(',') : []);
return dbExercises.filter((e) => visibleIds.has(e.id));
}, [workout, exerciseIdsKey]); // le chrono re-render chaque seconde : on ne refiltre pas
Quand un calcul réel (filtrer, trier, construire un Set) se retrouve dans un composant qui re-render souvent. On la reconnaît à la forme useMemo(() => …, [ingrédients]) : mêmes dépendances, donc même référence rendue.
const [email, setEmail] = useState('');
// …
<Input
placeholder="Email"
value={email} // l'état React est la SOURCE DE VÉRITÉ du champ
onChangeText={setEmail} // chaque frappe met l'état à jour (la chaîne, pas un event)
editable={!isLoading} // champ gelé pendant une requête
/>
Pour toute saisie utilisateur. On la reconnaît au couple value={x} / onChangeText={setX} : le champ n'a pas de mémoire propre, il n'affiche que l'état. Son jumeau web est la formule « Un état pour tout le formulaire ».
export default function AppLayout() {
const isAuthenticated = useAuthStore((s) => s.isAuthenticated);
// LA GARDE : pas connecté → on redirige, aucun écran privé n'est rendu.
if (!isAuthenticated) return <Redirect href="/sign-in" />;
return (
<Stack screenOptions={{ headerShown: false }}>
<Stack.Screen name="(tabs)" />
</Stack>
);
}
Quand un groupe entier d'écrans doit être protégé par une même condition. On la reconnaît à un _layout.tsx qui retourne un <Redirect> au lieu de son conteneur : trois lignes protègent tout le sous-arbre.
function Button({ className, variant, size, ...props }: ButtonProps) {
return (
<Pressable
className={cn(
props.disabled && 'opacity-50', // a) conditionnel
buttonVariants({ variant, size }), // b) le style CVA
className // c) l'appelant EN DERNIER → il surcharge
)}
{...props} // onPress, disabled, children… transmis
/>
);
}
Pour toute brique d'un système de composants. On la reconnaît à la signature qui collecte (...props) puis réétale ({...props}), et à l'ordre du cn() où className finit toujours dernier — la dernière classe gagne.
export const useAuthStore = create<AuthState>()((set) => ({
user: null,
isAuthenticated: false,
setUser: (user) =>
set({
user, // LA source de vérité
isAuthenticated: user !== null, // dérivé : les deux ne peuvent se contredire
}),
}));
// À la lecture : un sélecteur = on ne s'abonne QU'À cette tranche.
const isAuthenticated = useAuthStore((s) => s.isAuthenticated);
Quand une donnée est partagée par des écrans éloignés. On la reconnaît à un objet « valeurs + fonctions », où les champs calculables sont recalculés dans l'action, et à la lecture par sélecteur (s) => s.champ.
// PHOTO : lecture unique → une Promesse. Marqueurs : await + .fetch()
export async function getActiveWorkout(userId: string) {
const rows = await database.get('workouts')
.query(Q.where('user_id', userId), Q.where('completed_at', null)).fetch();
return rows[0] ? workoutToPlain(rows[0]) : null;
}
// DIRECT : MÊME requête, seul le verbe final change → un Observable.
export function observeActiveWorkout(userId: string) {
return database.get('workouts')
.query(Q.where('user_id', userId), Q.where('completed_at', null)).observe();
}
Chaque fois qu'on lit la base locale. On la reconnaît au verbe final : .fetch() = une photo figée, pour un traitement ponctuel ; .observe() = un flux qui ré-émet, pour un écran qui doit rester à jour.
-- Sans cette ligne, aucune politique ne s'applique.
ALTER TABLE public.workouts ENABLE ROW LEVEL SECURITY;
-- FOR ALL = lire, insérer, modifier, supprimer.
CREATE POLICY "Users see own workouts" ON public.workouts FOR ALL
-- Postgres ajoute ce filtre à CHAQUE requête : incontournable depuis l'app.
USING ((select auth.uid()) = user_id);
Dès qu'une table contient les données de plusieurs utilisateurs. On la reconnaît au motif auth.uid() = user_id : la sécurité vit dans la base, jamais dans un if côté client.
// Dans le hook : des DONNÉES prêtes à afficher + des ACTIONS à brancher.
return {
currentExercise,
isLastExercise: exerciseCount > 0 && safeIndex === exerciseCount - 1,
elapsedSeconds,
goToNextExercise: nextExercise, // action du store, simplement ré-exposée
};
// Dans l'écran : une ligne. Il ignore qu'il existe un store et une base.
const { currentExercise, elapsedSeconds, goToNextExercise } = useActiveWorkout();
Quand un écran a besoin de plusieurs données et de plusieurs gestes. On la reconnaît au return { … } mêlant valeurs et fonctions, et à l'écran qui déstructure tout en une seule ligne.