JavaScript moderne & l'asynchrone
Tu as un JavaScript correct — bravo, c'est déjà l'essentiel. Mais quand tu ouvres un fichier de
Halterofit, tu butes sur des symboles bizarres : des =>, des ..., des
?., des await. Ce ne sont pas de nouveaux langages : c'est du JavaScript
moderne, plus une façon précise de gérer le temps qui passe (l'asynchrone). On
démystifie tout ça, lentement, avec beaucoup de mots et peu de magie.
1. Pourquoi ce module avant tout le reste
Voici une vérité un peu frustrante : ce qui rend le code de ton app difficile à lire au début,
ce n'est presque jamais React, ni TypeScript, ni l'architecture. C'est la syntaxe
JavaScript moderne et l'asynchrone. Ce sont deux couches qui se sont
ajoutées au langage que tu connais, et tant qu'on ne te les a pas expliquées explicitement, elles
ressemblent à du bruit. Une ligne comme const { email } = props ou
workout?.exercises ?? [] n'utilise aucune librairie, aucun framework — juste du
JavaScript récent. Si tu sais lire ces lignes, 70 % du « mur » tombe.
Pourquoi « moderne » ? Parce que JavaScript a énormément évolué depuis 2015 (la version qu'on
appelle ES6, puis chaque année une nouvelle fournée). Le JS qu'on apprend dans un vieux cours
(var, function, des callbacks imbriqués) marche encore, mais plus
personne n'écrit comme ça dans une app récente. Halterofit utilise la syntaxe d'aujourd'hui
partout. Donc apprendre à la lire n'est pas optionnel : c'est la langue dans laquelle ton app
est écrite.
Ton objectif ici n'est pas de devenir capable d'écrire tout ça de tête. C'est de reconnaître chaque construction quand elle passe sous tes yeux, et de pouvoir te dire « ah, ça, c'est juste une destructuration » sans paniquer. La reconnaissance vient bien avant la production. On vise la lecture fluide.
Le module se découpe en deux grandes parties. D'abord, la syntaxe moderne : une petite collection de raccourcis d'écriture, chacun résolvant un agacement concret. Puis l'asynchrone : la partie la plus conceptuelle, qui explique comment le langage gère les choses qui prennent du temps (une requête réseau, par exemple) sans figer l'écran. La première partie, c'est du vocabulaire. La seconde, c'est une vraie idée à comprendre. Prends ton temps sur la seconde.
2. const et let — et l'adieu à var
Commençons par le plus simple. Une variable, c'est une boîte qui contient une valeur. En JS
moderne, on déclare cette boîte avec deux mots-clés : const et
let. Le troisième, var, est l'ancien mot-clé — tu le verras dans de
vieux exemples sur internet, mais jamais dans Halterofit. Comprendre pourquoi il a
disparu t'aide à comprendre les deux autres.
-
const(de « constante ») : la boîte ne pourra plus être réassignée. Une fois que tu y as mis une valeur, le nom pointe sur cette valeur jusqu'à la fin. C'est le choix par défaut, et de loin le plus fréquent dans l'app. -
let: la boîte peut être réassignée plus tard. On l'utilise quand on sait qu'on va changer la valeur (un compteur, un drapeau qui passe defalseàtrue).
const MIN_PASSWORD_LENGTH = 8; // ne changera jamais → const
let cancelled = false; // on va le passer à true plus tard → let
// ...
cancelled = true; // autorisé car c'est un `let`
// MIN_PASSWORD_LENGTH = 9; // INTERDIT : réassigner un `const` = erreur
Ces deux lignes viennent vraiment de l'app (le minimum de mot de passe, et un drapeau d'annulation
dans un hook). La règle de lecture : par défaut tout est const ; un
let est un signal « attention, cette valeur va bouger ».
Subtilité importante : const empêche de réassigner le nom, mais si la boîte
contient un objet ou un tableau, tu peux toujours en modifier le contenu. Autrement dit,
const tableau = [] puis tableau.push(1) est permis. Ce qui est interdit,
c'est tableau = [1] (lui donner une toute nouvelle valeur). On reverra cette nuance
avec l'immutabilité plus loin.
Pourquoi a-t-on abandonné var ? Parce qu'il avait des règles de « portée » (où la
variable existe) surprenantes, qui causaient des bugs sournois. const et
let ont des règles plus prévisibles : la variable n'existe qu'à l'intérieur des
accolades { } où elle est déclarée. Tu n'as pas besoin de retenir les détails — juste
de savoir que const/let sont les bons outils et que var
appartient au passé.
3. Les fonctions fléchées () => {}
Voici le symbole qui déroute le plus au début : la petite flèche =>. C'est une
autre façon d'écrire une fonction, appelée fonction fléchée. Compare les deux
écritures, elles font exactement la même chose :
// Ancienne écriture, que tu connais :
function additionner(a, b) {
return a + b;
}
// Écriture fléchée, équivalente :
const additionner = (a, b) => {
return a + b;
};
// Et même, quand le corps est un simple `return`, on peut tout compacter :
const additionner = (a, b) => a + b; // le `return` est implicite
Lis => comme « ... donne ... » : « a et b donnent a + b ». Les paramètres sont à
gauche de la flèche, ce que la fonction renvoie est à droite.
Au-delà de la concision, il y a une idée plus profonde, et c'est elle qui compte vraiment pour lire React : en JavaScript, une fonction est une valeur comme une autre. Tu peux la ranger dans une variable, la passer en argument à une autre fonction, la stocker dans un objet. C'est exactement ce qui se passe partout dans l'app : on donne une fonction à un bouton pour lui dire « voici quoi faire quand on te presse ».
// Dans le sign-up : on PASSE une petite fonction fléchée à `onPress`.
// On ne l'appelle pas nous-mêmes — on la donne, et React l'appellera
// au moment où l'utilisateur presse le bouton "afficher le mot de passe".
<Pressable onPress={() => setShowPassword(!showPassword)}>
{/* ... */}
</Pressable>
() => setShowPassword(!showPassword) est une fonction sans paramètre
(parenthèses vides) dont le corps inverse l'état. On la passe comme une étiquette : « tiens, le
jour où on te presse, exécute ça ».
onPress={() => faire()} et pas onPress={faire()} ?
Énorme piège de débutant, donc on le pose tout de suite. faire() avec les
parenthèses appelle la fonction tout de suite (au moment du rendu) et donne son
résultat. () => faire() emballe l'appel dans une nouvelle
fonction qu'on remet à plus tard. On veut donner la recette, pas le plat déjà
cuisiné. C'est pour ça qu'on voit autant de () => autour des appels dans le
JSX.
4. Les littéraux de gabarit (template strings)
Tu sais déjà coller des morceaux de texte avec le + :
'Bonjour ' + nom + ' !'. Ça marche, mais ça devient vite illisible dès qu'il y a
plusieurs trous à remplir. Le JS moderne offre une écriture beaucoup plus agréable : le
littéral de gabarit. On encadre le texte avec des accents graves (le
caractère `, la touche à gauche du 1 sur la plupart des claviers) au lieu de
guillemets, et on insère des valeurs avec ${ }.
const nom = 'Patrick';
// Ancienne façon, avec des + :
const message = 'Bonjour ' + nom + ', tu as ' + 3 + ' séances.';
// Façon moderne, avec un gabarit `...` et des ${ } :
const message = `Bonjour ${nom}, tu as ${3} séances.`;
Tout ce qui est entre ${ et } est du JavaScript : une variable, un calcul,
un appel de fonction. Le reste est du texte pur. Bien plus lisible que des + à la
chaîne.
On en trouve directement dans les messages d'erreur des validateurs de l'app, où l'on glisse une limite chiffrée dans la phrase :
// `${MAX_EMAIL_LENGTH}` insère la valeur de la constante DANS le texte.
return `Email cannot exceed ${MAX_EMAIL_LENGTH} characters`;
// ...
return `Password must be at least ${MIN_PASSWORD_LENGTH} characters`;
Avantage bonus : un littéral de gabarit peut s'étendre sur plusieurs lignes sans bricolage. Les retours à la ligne tapés à l'intérieur sont conservés.
5. La destructuration — le cœur de la lecture moderne
Si tu ne dois maîtriser qu'une chose de ce module, c'est celle-ci. La destructuration est partout dans React, et tant qu'on ne te l'a pas montrée, des lignes entières restent illisibles. L'idée : au lieu d'aller chercher les valeurs d'un objet ou d'un tableau une par une, on les déballe en une seule ligne, en décrivant la forme de ce qu'on attend.
Commençons par un objet. Imagine un objet props qui contient plusieurs champs :
const props = { email: 'a@b.com', password: '12345678', isLoading: false };
// SANS destructuration : on pioche champ par champ.
const email = props.email;
const password = props.password;
// AVEC destructuration : on déballe en une ligne.
// "extrais les champs email et password de props, et fais-en des variables".
const { email, password } = props;
Les accolades { } à gauche du = ne créent pas un objet : elles
décrivent quels champs on veut en sortir. Les noms entre accolades doivent correspondre aux noms des
champs dans l'objet.
Pour les tableaux, c'est pareil mais avec des crochets [ ], et là
c'est la position qui compte, pas le nom. Le premier élément, le deuxième, etc. Et c'est
précisément la forme qu'on rencontre des dizaines de fois en React, avec useState :
// useState() RENVOIE un tableau de deux éléments :
// [0] la valeur actuelle, [1] une fonction pour la changer.
// On les déballe d'un coup et on les nomme comme on veut.
const [email, setEmail] = useState('');
const [isLoading, setIsLoading] = useState(false);
// ▲ ▲
// la valeur la fonction qui la met à jour
Ces deux lignes viennent telles quelles du sign-up. Le couple [valeur, setValeur] est
LE motif de lecture de React. On en reparle longuement dans le module sur les hooks d'état.
La destructuration accepte aussi des valeurs par défaut : si le champ est absent
(vaut undefined), on prend la valeur de repli. Tu le verras dans les signatures de
composants, par exemple variant = 'default' :
// On déballe className et variant des props. Si `variant` n'est pas
// fourni, il vaudra 'default'. Le `...props` est expliqué juste après.
function Text({ className, variant = 'default', ...props }) {
// ...
}
Lis le = 'default' comme « ... ou bien 'default' si on ne me donne rien ». Très courant
pour donner des réglages par défaut aux composants.
Quand tu vois { } ou [ ] à gauche d'un =,
ce n'est pas une création, c'est un déballage. Tu lis « je sors ces morceaux de
l'objet/du tableau de droite et je leur donne ces noms ». Accolades = par nom de champ ; crochets
= par position.
6. Le spread / rest ... et l'immutabilité
Les trois petits points ... ont deux usages jumeaux. C'est le même symbole, et le sens
se devine selon l'endroit où il apparaît. Ne te laisse pas intimider : une fois les deux cas vus,
c'est mécanique.
Usage 1 — le « spread » (étaler). Quand ... est utilisé pour
construire un objet ou un tableau, il « déverse » le contenu d'un autre dedans. C'est la
façon idiomatique de copier en ajoutant ou modifiant des éléments :
// Copier un objet en changeant un champ :
const base = { email: 'a@b.com', isLoading: false };
const suivant = { ...base, isLoading: true };
// suivant = { email: 'a@b.com', isLoading: true } ← copie + champ modifié
// Copier un tableau en ajoutant un élément à la fin :
const ids = ['ex1', 'ex2'];
const plus = [...ids, 'ex3'];
// plus = ['ex1', 'ex2', 'ex3'] ← un NOUVEAU tableau, ids reste intact
...base = « étale ici tous les champs de base ». Ce qui vient après peut écraser un
champ (ici isLoading). On crée toujours une nouvelle valeur ; l'original
n'est pas touché.
Usage 2 — le « rest » (le reste). Quand ... apparaît dans une
destructuration ou les paramètres d'une fonction, il regroupe tout ce qui n'a pas été nommé
explicitement. Tu l'as déjà croisé deux sections plus haut :
// On nomme className et variant ; ...props ramasse TOUT le reste des props.
function Text({ className, variant = 'default', ...props }) {
// ...props contient tous les autres champs non listés.
// On peut ensuite les re-étaler sur un composant interne : {...props}
}
Même symbole, sens opposé : ici il collecte au lieu d'étaler. À gauche du
= (déballage) il ramasse le reste ; à droite (construction) il déverse.
Voici l'idée capitale derrière le spread, et elle est au cœur de React. Muter,
c'est modifier une valeur existante sur place (tableau.push(x) ajoute dans le tableau
d'origine). Recréer, c'est fabriquer une nouvelle valeur à partir de l'ancienne
([...tableau, x] laisse l'original tranquille et en rend une copie agrandie). En
React, on recrée presque toujours, parce que React repère un changement en comparant si la valeur
est un nouvel objet. Si tu mutes l'ancien sur place, React ne « voit » rien changer et ne
redessine pas l'écran. Retiens la maxime : « on ne mute pas, on recrée ».
7. Chaînage optionnel ?. et coalescence ??
Ces deux opérateurs résolvent le même problème : les valeurs qui pourraient ne pas
exister. Dans une vraie app, une donnée peut être null ou
undefined le temps qu'un chargement se termine. Tenter de lire un champ sur du
« rien » fait planter le programme. Ces deux outils rendent ça sûr et lisible.
Le chaînage optionnel ?. Place un point d'interrogation avant le
point d'accès. S'il y a quelque chose à gauche, on continue ; si c'est null/undefined,
toute l'expression s'arrête net et vaut undefined, sans planter.
// Si workout est null (encore en chargement), workout?.exercises
// vaut undefined — au lieu de faire planter "lecture de exercises sur null".
const liste = workout?.exercises;
// On le voit aussi dans le sign-up sur une référence d'input :
emailRef.current?.focus();
// "s'il y a bien un input ciblé, mets-y le focus ; sinon, ne fais rien".
Lis ?. comme « ... s'il existe, alors ... ». C'est un garde-fou contre le crash le plus
courant : « impossible de lire une propriété de undefined ».
L'opérateur de coalescence des nuls ?? dit « prends la valeur de
gauche, mais si elle est null/undefined, prends celle de droite à la
place ». C'est la façon propre de fournir une valeur de repli. Combiné au ?., il forme
un duo qu'on lit ensemble :
// "les exercices du workout, OU un tableau vide si workout (ou exercises)
// n'existe pas". Résultat : dbExercises est TOUJOURS un tableau utilisable.
const dbExercises = workout?.exercises ?? [];
Cette seule ligne combine les deux : ?. protège l'accès, ?? donne le repli.
Elle garantit qu'on a toujours un tableau, même pendant le chargement — donc on peut enchaîner un
.filter(...) sans crainte.
?? n'est pas tout à fait ||
On serait tenté d'utiliser le vieux || pour le repli. La différence : ||
bascule sur la droite pour toute valeur « fausse » — y compris 0, la chaîne
vide '' ou false. ?? ne bascule que pour
null/undefined. Donc si 0 ou '' sont des
valeurs légitimes, ?? est le bon choix. C'est plus précis, et c'est pour ça que le
code récent le préfère.
8. Court-circuit && / || et le ternaire
Tu connais && (ET) et || (OU) comme tests booléens. En JavaScript,
ils ont un superpouvoir qu'on exploite tout le temps en React : ils renvoient une
valeur, pas seulement true/false. C'est le
« court-circuit ». a && b : si a est faux, on s'arrête et on
renvoie a ; sinon on renvoie b. On s'en sert pour
l'affichage conditionnel : « si telle condition, alors affiche tel élément ».
// "si error n'est pas une chaîne vide, alors affiche le <Text>".
// Si error vaut '', l'expression s'arrête à gauche et rien n'est rendu.
{error !== '' && <Text className="text-destructive">{error}</Text>}
Le motif condition && <Composant /> est l'idiome n°1 de l'affichage
conditionnel dans le JSX. Pas de if, juste un &&.
Quand il faut choisir entre deux rendus (l'un OU l'autre), on emploie l'opérateur
ternaire : condition ? siVrai : siFaux. C'est un if/else
condensé en une expression. Le sign-up s'en sert pour le bouton « Créer un compte » : pendant le
chargement, un sablier ; sinon, le texte.
// Pendant le chargement → un indicateur tournant ; sinon → le texte.
{isLoading ? (
<ActivityIndicator color={Colors.primary.foreground} />
) : (
<Text>Create Account</Text>
)}
Lis-le « est-ce que isLoading ? si oui le sablier, sinon le texte ». Le ? sépare la
condition du premier choix, le : sépare les deux choix.
9. Les méthodes de tableau : map, filter, find, includes
On manipule des listes en permanence : des exercices, des séries, des erreurs. Le JS moderne
fournit des méthodes qui parcourent un tableau pour toi, sans for manuel. Chacune prend
une fonction en argument (souvent fléchée — d'où l'importance de la section 3) et
l'applique à chaque élément. Les quatre que tu croiseras le plus :
-
map: transforme chaque élément et rend un nouveau tableau de même taille. En React, on l'emploie pour transformer une liste de données en une liste de composants à l'écran. -
filter: garde seulement les éléments qui passent un test, et rend un tableau plus petit (ou égal). -
find: rend le premier élément qui passe le test (un seul élément, pas un tableau), ouundefinedsi aucun ne correspond. -
includes: rendtrue/falseselon qu'une valeur est présente ou non dans le tableau.
// filter : ne garder que les exercices dont l'id est encore visible.
// Pour chaque élément `e`, on teste s'il passe — sinon on le jette.
return dbExercises.filter((e) => visibleIds.has(e.id));
// map (illustration) : transformer chaque nombre en son double.
const doubles = [1, 2, 3].map((n) => n * 2); // [2, 4, 6]
// includes (illustration) : la valeur est-elle dans le tableau ?
['a', 'b'].includes('b'); // true
Le filter est tiré du vrai hook des entraînements. Le point commun de toutes ces
méthodes : elles ne touchent pas le tableau d'origine, elles en rendent un nouveau (sauf
find/includes qui rendent un élément ou un booléen). Encore l'immutabilité.
10. Les modules : import et export
Une app n'est pas un seul gros fichier : c'est des centaines de petits fichiers qui se passent des morceaux de code. Le système qui organise ça s'appelle les modules. Un fichier exporte ce qu'il veut rendre disponible aux autres, et un autre fichier importe ce dont il a besoin. C'est le tout début de chaque fichier de l'app, ces lignes que tu survolais sans les lire.
// Dans validators/auth.ts — on EXPORTE une fonction, donc d'autres
// fichiers pourront s'en servir :
export function getEmailError(email: string): string | null {
// ...
}
// Dans sign-up.tsx — on IMPORTE ce dont on a besoin, par leur nom,
// depuis le module où ils ont été exportés :
import { getEmailError, getPasswordError } from '@/utils/validators';
import { useState } from 'react';
Les { } dans un import sont encore de la destructuration : on choisit
nommément ce qu'on prend. Le @/ est un raccourci vers le dossier source du projet
(détaillé dans le module sur la structure du projet).
Regarde le haut de sign-up.tsx : une vingtaine de lignes d'import. Elles
te disent exactement de quoi cet écran dépend : des composants d'UI
(Button, Input), des validateurs, le service signUp, des
outils de navigation. Lire les imports d'un fichier, c'est lire sa table des matières avant de
plonger dedans. C'est une habitude de lecture précieuse.
11. L'asynchrone — le cœur conceptuel du module
On change de registre. Tout ce qui précède, c'était du vocabulaire. Maintenant, une vraie idée à comprendre : comment JavaScript gère les choses qui prennent du temps. Créer un compte appelle un serveur ; charger un entraînement lit une base de données. Ces opérations durent des centaines de millisecondes, parfois des secondes. La question est : que fait le programme pendant ce temps ?
Pour répondre, il faut un fait surprenant : JavaScript est mono-thread. Il n'a qu'un seul fil d'exécution, une seule chose en train de se faire à la fois. Imagine un restaurant avec un seul cuisinier. Il ne peut tenir qu'une casserole à la fois. Si ce cuisinier décidait de rester planté devant le four à attendre que le plat cuise, toute la cuisine s'arrêterait : aucune autre commande ne pourrait avancer. C'est exactement la situation de JavaScript.
La solution du cuisinier malin : il met le plat au four, lance un minuteur, et passe immédiatement à la commande suivante. Quand le minuteur sonne, il revient s'occuper du plat. Il ne reste jamais bloqué à attendre. JavaScript fait pareil grâce à la boucle d'événements : quand une opération longue est lancée (réseau, base de données), JS ne l'attend pas sur place — il continue le reste, et reviendra traiter le résultat quand il sera prêt. Un seul cuisinier, mais jamais immobile.
Pourquoi est-ce vital dans une app mobile ? Parce que ce même cuisinier unique est aussi celui qui dessine l'écran et réagit à tes doigts. S'il reste bloqué à attendre une réponse réseau, il ne peut plus redessiner ni répondre aux touches : l'écran gèle. Le scroll se fige, les boutons ne réagissent plus. C'est l'expérience la plus détestée des utilisateurs. D'où la règle absolue : dans une UI, on ne bloque jamais. Toute opération longue doit être asynchrone.
12. Callbacks → Promesses → async/await
Reste à savoir comment écrire « fais ceci, et quand ce sera prêt, fais cela ». JavaScript a connu trois générations d'outils pour ça. Les connaître t'aide à dater le code que tu lis et à comprendre pourquoi le style moderne existe.
- Les callbacks (1ʳᵉ génération). On passe une fonction (« rappelle-moi quand c'est fini »). Ça marche, mais quand on enchaîne plusieurs étapes, les fonctions s'imbriquent les unes dans les autres jusqu'à former une pyramide illisible, surnommée le « callback hell ».
-
Les Promesses (2ᵉ génération). Un objet qui représente un résultat
futur. On y attache des suites avec
.then(). Bien plus plat et lisible que les callbacks imbriqués. C'est encore très présent dans l'app. -
async/await(3ᵉ génération, l'actuelle). Une écriture qui fait ressembler le code asynchrone à du code normal, ligne après ligne. C'est ce qu'on préfère aujourd'hui pour sa lisibilité.
13. Les Promesses en détail
Une Promesse (Promise) est un objet qui dit : « je te promets un résultat, mais pas tout de suite ». Pense au ticket que te remet le comptoir d'un resto à emporter : tu n'as pas encore ton plat, mais tu as la garantie qu'il arrive. Une Promesse a exactement trois états :
- pending (en attente) : le travail est en cours, le résultat n'est pas encore là.
- fulfilled (tenue) : ça a réussi, le résultat est disponible.
- rejected (rompue) : ça a échoué, on a une erreur à la place.
Pour réagir, on attache des suites avec trois méthodes : .then() pour le succès,
.catch() pour l'échec, et .finally() pour ce qu'on veut faire dans
tous les cas (succès ou échec). Le vrai hook des entraînements illustre ça parfaitement :
// getWorkoutWithDetails(...) RENVOIE une Promesse (le chargement DB).
getWorkoutWithDetails(currentWorkoutId)
.then((data) => {
// ✅ fulfilled : on a reçu les données. `data` est le résultat.
if (!cancelled) setLoadedWorkout(data);
})
.catch((err) => {
// ❌ rejected : le chargement a échoué, on récupère l'erreur.
if (cancelled) return;
setFailedWorkoutId(currentWorkoutId);
handleError(err, 'useActiveWorkout');
});
On lit ça comme une suite : « charge le workout, puis (then) range les données ;
en cas d'échec (catch), note l'erreur ». Le data du
.then et le err du .catch sont les résultats que la Promesse
nous livre.
14. async/await — le « sucre syntaxique »
Les .then() restent un peu indirects à lire dès que les étapes s'enchaînent.
async/await est une écriture plus récente, par-dessus les Promesses, qui les rend
lisibles de haut en bas, comme du code normal. On appelle ça du « sucre
syntaxique » : ça n'ajoute aucun pouvoir nouveau (par-dessous, ce sont toujours des Promesses), ça
rend juste les choses plus douces à lire.
Deux mots-clés :
-
asyncdevant une fonction : il annonce « cette fonction est asynchrone et renverra une Promesse ». C'est le permis d'utiliserawaità l'intérieur. -
awaitdevant un appel qui rend une Promesse : il dit « mets en pause ici jusqu'à ce que le résultat arrive, puis donne-le-moi ». Crucial : cette pause ne bloque pas le cuisinier — il va faire autre chose en attendant, exactement comme la boucle d'événements le permet.awaitsuspend ta fonction, pas tout le programme.
Pour les erreurs, comme on n'a plus de .catch(), on emploie le bon vieux
try / catch / finally que tu connais peut-être déjà : on essaie
(try), on attrape l'erreur si ça casse (catch), et on
fait le ménage dans tous les cas (finally).
15. Mise en pratique : handleSignUp décortiqué
On rassemble tout. Voici le cœur de l'écran d'inscription, légèrement abrégé. C'est l'exemple
parfait d'async/await + try/catch/finally dans la vraie vie. Lis-le
lentement, on le commente juste après.
// `async` : cette fonction fait du travail asynchrone (appel serveur).
const handleSignUp = async () => {
setError('');
// ... validations locales d'abord (email, mot de passe, etc.) ...
const emailErr = getEmailError(email);
if (emailErr) {
setError(emailErr);
return; // on s'arrête net si la saisie est invalide
}
setIsLoading(true); // on allume le sablier AVANT l'attente
try {
// `await` : met en pause ICI jusqu'à la réponse du serveur,
// SANS geler l'écran. Si signUp réussit, on continue dessous.
await signUp(email, password, displayName);
router.replace({ pathname: '/(auth)/confirm-account', /* ... */ });
} catch (err) {
// On n'arrive ici QUE si signUp a échoué (Promesse rejetée).
setError(isOperationalError(err) ? err.userMessage : 'Something went wrong.');
} finally {
// Dans TOUS les cas (succès OU échec), on éteint le sablier.
setIsLoading(false);
}
};
Note le rôle parfait du finally : qu'on réussisse (on navigue ailleurs) ou qu'on
échoue (on affiche l'erreur), il faut toujours arrêter le chargement. C'est
exactement à ça que sert finally.
Suis le déroulé : on efface l'ancienne erreur, on valide la saisie (et on sort tôt avec
return si quelque chose cloche — voir le module sur TypeScript pour les types de retour).
Puis on allume le sablier, on await signUp(...) — c'est là que la pause non bloquante
se produit — et selon l'issue, soit on navigue vers la confirmation, soit on attrape l'erreur. Et
quoi qu'il arrive, le finally rallume le bouton. Toute la logique de « ça charge »,
« ça a marché », « ça a raté » tient dans cette structure. Tu la reverras dans presque tous les
écrans.
Tu viens de voir les deux écritures de l'asynchrone dans l'app : le hook des
entraînements emploie .then()/.catch() (style Promesses) et l'écran d'inscription
emploie async/await + try/catch. Ce sont deux écritures de la
même mécanique. Savoir lire les deux te suffit pour tout le code asynchrone du projet.
Voici une ligne réelle du hook des entraînements. Décompose-la mot à mot, à voix haute :
const dbExercises = workout?.exercises ?? [];
Questions : (1) Que vaut dbExercises si workout est
null ? (2) Pourquoi ne pas avoir écrit simplement workout.exercises ?
(3) Quelle garantie cette ligne donne-t-elle sur le type de dbExercises ?
Voir le corrigé
(1) Un tableau vide []. Le ?. fait
que workout?.exercises vaut undefined quand workout est
null, puis le ?? [] remplace ce undefined par
[].
(2) Parce que workout peut être null pendant le
chargement. Écrire workout.exercises dessus ferait planter le
programme (« lecture de exercises sur null »). Le ?. est le garde-fou.
(3) Qu'il sera toujours un tableau, jamais
null/undefined. C'est précieux : la ligne suivante peut alors faire un
.filter(...) en toute sécurité, sans revérifier.
1. Que déballe const [email, setEmail] = useState(''), et dans quel ordre ?
Une destructuration de tableau : useState rend un tableau de deux éléments, et on les prend par position. Position 0 = la valeur actuelle (email) ; position 1 = la fonction qui la met à jour (setEmail).
2. Quelle est la différence entre onPress={faire()} et onPress={() => faire()} ?
faire() appelle tout de suite la fonction (au rendu) et passe son résultat. () => faire() emballe l'appel dans une fonction qu'on donne, et que React exécutera seulement au moment de la pression. On veut presque toujours la seconde forme.
3. Pourquoi écrit-on [...ids, 'ex3'] plutôt que ids.push('ex3') en React ?
Par immutabilité : push mute le tableau d'origine sur place, et React ne « voit » alors aucun changement (même référence) donc ne redessine pas. [...ids, 'ex3'] crée un nouveau tableau, que React reconnaît comme un changement. On ne mute pas, on recrée.
4. À quoi sert le finally dans handleSignUp ?
À exécuter du code dans tous les cas, succès comme échec. Ici, setIsLoading(false) : qu'on navigue après réussite ou qu'on affiche une erreur, il faut toujours éteindre le sablier. Le finally garantit qu'on ne reste jamais bloqué en « chargement ».
5. await bloque-t-il toute l'application pendant l'attente ?
Non. await met en pause uniquement la fonction qui l'utilise, et rend la main pour que le reste (rendu, réactions tactiles) continue. C'est tout l'intérêt de l'asynchrone : attendre sans geler l'écran.
const/let (jamais var) ; () => = une fonction,
donc une valeur qu'on passe ; `${ }` = insérer dans du texte ;
{ } / [ ] à gauche d'un = = déballage (destructuration) ;
... = copier/étaler ou ramasser le reste, en recréant sans muter ;
?. = accès sûr, ?? = valeur de repli ; && = afficher
si vrai, le ternaire ? : = choisir entre deux. Côté asynchrone : un seul cuisinier qui
ne bloque jamais ; une Promesse a trois états (pending / fulfilled / rejected) ;
async/await + try/catch/finally est la façon moderne, lisible, de gérer
ce qui prend du temps.
Deux classiques qui te coûteront des heures si tu ne les guettes pas. Oublier
await : si tu écris signUp(...) sans await dans un
try, le code file à la ligne suivante avant que le serveur ait répondu, et
ton catch ne rattrapera jamais l'erreur (elle arrive trop tard). Le symptôme : « ça
marche parfois, ça rate sans message ». Muter au lieu de recréer : faire
tableau.push(x) puis ré-injecter ce même tableau dans React — l'écran ne se met pas à
jour, parce que React compare les références et voit « le même tableau ». Le réflexe : un spread
[...tableau, x] pour recréer.
Rien de tout ça n'est propre à React ni à Halterofit. La destructuration, le spread, le chaînage
optionnel, les Promesses et async/await sont du JavaScript standard :
tu les retrouveras dans n'importe quelle app web, n'importe quel back-end Node, n'importe quel
script moderne. Et le concept d'asynchrone non bloquant (« ne reste jamais à attendre ») existe
dans presque tous les langages, sous d'autres noms. Comprendre pourquoi on ne bloque pas
une UI est une intuition qui te suivra partout, bien au-delà de ce projet.