Laboratoire 4: Application météo
Introduction
Dans ce labo, nous verrons comment alimenter notre application React avec des données venant d’une API. Nous verrons aussi les méthodes de cycles de vie qui permettent de récupérer les données sans bloquer le rendu de notre composant. Le but sera de construire une application qui permet de :
- Comparer la météo entre 2 lieux
- Voir la météo d’un lieu grâce à une URL

Prérequis
Vous devrez aussi faire des requêtes avec votre application vers une API. Pour cela, vous aurez deux moyens de le faire: via axios ou via un fetch (natif).
L’API que nous utiliserons pour récupérer les données se nomme : Open Weather Map. Vous aurez besoin d’une clé pour pouvoir utiliser leur API. Vous avez besoin de vous inscrire pour en obtenir une. Nous utiliserons le plan GRATUIT pour faire le labo. Si vous rencontrez des difficultés pour récupérer votre clé, vous pouvez utiliser la documentation suivante : https://openweathermap.org/appid#get
La documentation de l’API est disponible à cette adresse : https://openweathermap.org/current. Vous pourrez y découvrir les paramètres à mettre dans votre requête, mais également comment interroger l’API (définir les unités pour la température ou choisir la langue de la réponse, par exemple).
Envoyer une requête à L’API
Maintenant que vous avez votre clé, vous aimeriez pouvoir faire une requête à l’API. Dans un premier temps, vous pouvez utiliser Postman pour pouvoir tester rapidement. Cependant, vous devrez le faire en JavaScript pour que votre application puisse communiquer avec l’API. Pour cela, vous avez 2 options :
- La méthode
fetchnative de JavaScript - Utiliser un module comme
axios
La première approche est actuellement supportée par presque tous les navigateurs web. Vous aurez donc besoin de consulter sa documentation pour voir son fonctionnement. Cependant, la méthode fetch se tient au minimum et vous devrez gérer vous-mêmes les paramètres de la requête. Vous pourrez vous aider de URLSearchParams qui permet de générer facilement les paramètres de la requête. Voici la documentation qui pourra vous aider (et une réponse StackOverflow) :
La seconde méthode consiste à utiliser un module nommé Axios qui permet de générer rapidement des requêtes grâce à un objet de configuration que vous passerez au constructeur. Si vous désirez utiliser cette approche vous devriez consulter la documentation du module à l’adresse suivante :
useTransition()
Le hook useTransition est un hook qui répond à une problématique de la mise à jour d'un état en React: la synchronicité.
En effet, quand vous mettez un état à jour, cette opération est synchrone.
Dans la plupart des cas, vous n'aurez aucun problème.
Le problème est par contre plus voyant si une des mises à jour est longue.
Dès lors, elle va bloquer la file d'attente des rendus de React et ainsi également l'interface.
Dans cet exemple, si vous cliquez sur Articles (lent), puis directement sur Contact, vous verrez que la page de contact met du temps à s'afficher.
Quand vous avez effectuez votre premier click sur Articles (lent), il y a eun premier rendu lent qui a bloqué la file d'attente de React.
Comme vous avez appuyez sur Contact, mais que le premier rendu était toujours en cours, ce dernier s'est retrouvé coincé dans cette fille d'attente.
La solution a ce problème consiste à avoir une approche asynchrone.
Dans ce deuxième exemple, si vous cliquez sur Articles (lent), puis directement sur Contact, vous verrez que la page de contact s'affiche directement.
Pour arriver à ce résultat, il nous faut un nouveau hook: useTransition.
Voici sa signature:
const [isPending, startTransition] = useTransition()
useTransition ne prend aucun paramètre et renvoie un tableau avec deux éléments: un booléan qui indique si la transition est en cours et une fonction permettant d'enclencher la transition.
La transition consiste simplement à effectuer un changement d'état de manière asynchrone.
Pour effectuer correctement notre changement d'état, nous devons effectuer ce dernier dans un appel à startTransition en le passant en callback (fonction synchrone uniquement).
starTransition(() => {
setState(...) // le changement est effectué dans le starTransition
});
Votre changement d'état doit être appelé "pendant" l'appel à starTransition !
Votre code ne fonctionnera pas si vous faites ceci:
startTransition(() => {
setTimeout(() => {
setState(...) // le changement d'état n'est plus effectué dans le startTransition !
}, 200);
});
Le problème vient de l'ordre d'exécution du thread JavaScript.
startTransitioncommence- le timer est créé
startTransitionse termine (il ne consite qu'à créer un timer)- le timer se termine
setStateest exécuté, car le timer se termine
L'approche correcte est la suivante:
setTimeout(() => {
startTransition(() => {
setState(...)
})
}, 200);
Ce qui nous donne:
- le timer est créé
- le timer se termine
startTransitioncommencesetStateest exécutéstartTransitionse termine
Un deuxième cas problème concerne les promesses qui souffrent du même problème: l'ordre d'exécution Votre code ne fonctionne pas si vous faites ceci:
startTransition(async () => {
await fonctionAsynchrone();
setState(...); // le changement d'état n'est plus effectué dans le startTransition !
})
Voici l'ordre d'éxécution:
startTransitioncommence- la fonction anonyme crée une promesse
startTransitionse termine- la promesse progresse (l'
awaita fini de bloquer) setStateest exécuté
La version correcte devrait être ceci:
await fonctionAsynchrone();
startTransition(() => {
setState(...);
})
Voici l'ordre d'éxécution:
fonctionAsynchronebloque (await)fonctionAsynchroneest terminé, donc on progressestartTransitioncommencesetStateest exécutéstartTransitionse termine
C'est pour cette raison que startTransition doit prendre une fonction synchrone comme argument !

Enfin, finisons sur un petit point de vocabulaire.
L'ensemble des fonctions appelées dans un startTransition sont appelées Action.
D'ailleurs, il est conseillé d'utiliser le mot Action dans le nom des fonctions utilisées dans le startTransition.
Si vous utilisez des fonctions pouvant renvoyer des erreurs, pensez à utiliser des try/cacth !
Sinon l'erreur pourrait se propager (si vous n'avez aucun mécanisme de contrôle d'exception) et faire planter votre application !
useActionState()
Le hook useActionState permet d'effectuer des changements d'états en utilisant une Action.
Voici sa signature:
const [state, dispatchAction, isPending] = useActionState(reducerAction, initialState, permalink?);
Commençons par les valeurs de retour:
- State est l'état renvoyé par
useActionStatelors de l'initialisation ou l'état renvoyé parreducerAction dispatchActionest une fonction qui permet d'envoyer une action àreducerAction- isPending est un boolean qui indique si la transition est en cours ou non.
Maintenant, les paramètres de useActionState:
reducerActionest une fonction (synchrone ou asynchrone) qui va recevoir 2 paramètres:- l'état précédent
- l'action et cette fonction renverra un état en appliquant l'action à l'état précédent
- l'état initial
permalinkest paramètre optionnnel utilisé si vous utilisez le rendu côté serveur, ce qui n'est pas notre cas. Vous pouvez donc l'oublier.
Cela ne vous rappelle pas Redux ?
dispatchAction doit être utilisé dans une Action (voir le hook précédent) ou dans une propriété action dans certains éléments.
C'est ce dernier point qui va nous intéresser.
En effet, l'envoie d'un formulaire est parfois un peu fastidieux.
Vous devez:
- préparer le formulaire (au niveau du code)
- récupérer les données du formulaire
- préparer les données pour l'envoie
- envoyer les données
- indiquer à l'utilisateur qu'on attend la réponse du serveur
- réceptionner la réponse du serveur
- traiter la réponse du serveur
- Mettre à jour l'état du composant
- indiquer à l'utilisateur qu'on a terminé avec l'envoie du formulaire
Notre useActionState va nous permettre de gérer quelques points pour nous, ainsi que la propriété action.
export default function Search() {
function search(formData) {
const query = formData.get("query");
alert(`You searched for '${query}'`);
}
return (
<form action={search}>
<input name="query" />
<button type="submit">Search</button>
</form>
);
}
Dans ce code, nous voyons que passons search comme valeur de la propriété action au niveau du formulaire.
Vous ne voyez pas d'appel à e.preventDefault(). C'est normal, React le fait pour nous, car nous utilisons la propriété action.
Ici, nous voyons que React va récupérer les données du formulaire et les mettre dans un formData pour nous.
Nous n'en discuterons pas dans ce laboratoire, car ce dernier sera utilisé et présenté dans le prochain.
En effet, l'API que nous allons utiliser ne s'attend pas à recevoir un formData.
Par contre, nous pouvons nous amuser à combiner ce code avec le hook useActionState pour rendre le formulaire plus dynamique.
export default function Search() {
const [state, dispatchAction, isPending] = useActionState(async (previousState, actionPayload) => {
try {
// appels à des fonctions
return {...} // votre nouveau state
} catch (err) {
// gestion des erreurs
return {...} //votre nouveau state
}
}, initialState);
function search(formData) {
const query = formData.get("query");
alert(`You searched for '${query}'`);
}
return (
<form action={search}>
<input name="query" />
<button type="submit" disable={isPending}>Search</button>
</form>
);
}
Ce qui nous donne ceci:
- préparer le formulaire (au niveau du code)
- récupérer les données du formulaire (fait par React)
- préparer les données pour l'envoie (fait par React)
- envoyer les données (fait par React)
- indiquer à l'utilisateur qu'on attend la réponse du serveur (en partie par React)
- réceptionner la réponse du serveur
- traiter la réponse du serveur
- Mettre à jour l'état du composant
- indiquer à l'utilisateur qu'on a terminé avec l'envoie du formulaire (fait par React)
Vous voyez donc qu'une bonne partie est gérée par React et vous facilitera la tâche. Evidemment, nous pousserons ce hook plus loin dans le prochain laboratoire.
useEffect()
Ce troisième hook va nous permettre de rajouter un "effet" à notre composant. Il s'agit d'une fonction qui sera enclenchée à chaque fois qu'une des dépendances sera modifiée. useEffect(setup, dependencies?) prend deux paramètres: la fonction de setup et un tableau de variables représentant les dépendances (optionnel). Il va nous permettre de gérer tout ce qui est "externe" à React, notamment les appels à l'API !
La fonction de setup est une fonction représentant l'action à effectuer. Le comportement de l'effet variera en fonction de la valeur de retour de cette fonction ! Si la fonction ne renvoie rien, l'effet est juste appliqué. Par contre, si la fonction renvoie une autre fonction, alors cette dernière sera utilisée avant chaque rendu (sauf la première fois que le composant est rendu). Cette "autre fonction" est appelée fonction de nettoyage et permet de supprimer les ressources utilisées et non controllées par React: un websocktet, un timer, un interval, une animation, une ressource d'une librairie, etc.
La première approche sera utilisée lorsque que la ressource ne persiste pas (exemple: une requête à une API), tandis que la seconde sera utilisée dans les autres cas.
Voici un exemple où le composant ouvre une connexion et cette connexion persiste (pour recevoir des messages en "temps réel"). Il faut supprimer la connexion quand le composant est démonté (sinon, elle restera ouverte tant que l'utilisateur ne ferme pas l'onglet):
import { useEffect } from 'react';
import { createConnection } from './chat.js';
function ChatRoom({ roomId }) {
const [serverUrl, setServerUrl] = useState('https://localhost:1234');
useEffect(() => {
const connection = createConnection(serverUrl, roomId);
connection.connect();
return () => { // Fonction de nettoyage
connection.disconnect();
};
}, [serverUrl, roomId]);
// ...
}
Le deuxième paramètre est une liste de dépendances. Le principe est simple: React va surveiller chacune des variables présentes dans le tableau. Dès qu'une des variables change de valeur, le useEffect va s'enclencher. Par contre, si vous ne précisez rien, l'effet s'enclenchera à chaque nouveau rendu de votre composant. Si vous mettez un tableau vide, alors l'effet ne s'enclenchera qu'au premier rendu de votre composant (lorsqu'il est créé).
En résumé:
| Tableau de dépendances | Signification |
|---|---|
| Tableau vide | L'effet ne s'enclenchera qu'à l'apparition de votre composant sur la page web (montage) |
| Aucun tableau | l'effet s'enlenchera à tous les rendus de votre composant |
| Tableau avec des variables | l'effet s'enclenchera à l'apparition de votre composant sur la page web ET à chaque fois qu'une des variables de votre tableau changera de valeur |
Et le cycle de vie ?
Comme pour les composants avec les classes, on peut représenter le cycle de vie avec les hooks ! Le voici:

Exercice
Maintenant que vous avez les informations nécessaires disponibles, il est temps de passer à l’exercice. Vous devrez faire 2 vues :
- Une première qui permet de comparer la météo de 2 villes
- Une deuxième qui affiche seulement la météo d’une ville précisée dans l’URL
Vous devrez également gérer :
- Si l’utilisateur tape le nom d’une ville inconnue
- Un affichage spécifique durant les temps de chargement
Enfin, il vous est demandé de faire du code "propre". Il y a beaucoup de points communs entre les 2 vues. A vous de faire en sorte de ne pas dupliquer inutilement du code. Ne vous préoccupez pas de la "beauté" de votre application, ce n’est pas le but de l’exercice. Voici à quoi vous devriez arrivés : Pour la comparaison de ville :
Si on tape l’URL avec le nom de la ville (ex : /city/namur) :