Module 2Construire une application React
Chapitre 10 sur 18
Consommer les API
Appeler une API avec axios et fetch, async/await, useEffect et componentDidMount, et le TP des utilisateurs et de leurs posts.
- ≈ 160 min
- 6 leçons
- 3 vidéos
- 4 exercices
Module 2Construire une application React
Chapitre 10 sur 18
Appeler une API avec axios et fetch, async/await, useEffect et componentDidMount, et le TP des utilisateurs et de leurs posts.
Objectifs
Avec ce chapitre, tu sauras…
.then() puis avec async/await ;useEffect(…, []) dans une fonction, componentDidMount() dans une classe ;Leçon 1
Ajax met à jour une partie de la page en envoyant une requête au serveur, sans recharger toute la page (chapitre 2).
React n'a pas d'outil intégré pour faire des requêtes Ajax. On utilise :
fetch() ;L'API de test JSONPlaceholder
Comme fournisseur d'API, on peut utiliser nos propres API (sur un serveur PHP, Laravel…) ou l'API en ligne gratuite https://jsonplaceholder.typicode.com. Ouvre https://jsonplaceholder.typicode.com/todos dans le navigateur : l'API renvoie des données JSON.
| Endpoint | Renvoie un tableau de… |
|---|---|
/todos | { "userId": 1, "id": 1, "title": "delectus aut autem", "completed": false } |
/comments | { "postId": 1, "id": 1, "name": "id labore ex et quam laborum", "email": "Eliseo@gardner.biz", "body": "laudantium enim…" } |
/albums | { "userId": 1, "id": 1, "title": "quidem molestiae enim" } |
/photos | { "albumId": 1, "id": 1, "title": "accusamus beatae…", "url": "https://via.placeholder.com/600/92c952", "thumbnailUrl": "https://via.placeholder.com/150/92c952" } |
/posts | { "userId": 1, "id": 1, "title": "…", "body": "…" } |
/users | { "id": 1, "name": "Leanne Graham", "username": "Bret", "email": "Sincere@april.biz", "address": { "street": "Kulas Light", "suite": "Apt. 556", "city": "Gwenborough", "zipcode": "92998-3874", "geo": { "lat": "-37.3159", "lng": "81.1496" } }, … } |
Leçon 2
Vidéo · À regarder
FRApprendre React en 1 heure - Cours complet débutant
Comment Coder · YouTube
Leçon 3
| axios | fetch | |
|---|---|---|
| installation | npm install axios | rien (intégré au navigateur) |
| les données | res.data (déjà converti) | await response.json() (à convertir) |
| erreur HTTP 404 / 500 | rejette la promesse (va dans catch) | ne rejette pas : vérifier response.ok |
Vidéo · À regarder
Leçon 4
| Composant | Où mettre l'appel ? | Où garder les données ? |
|---|---|---|
| fonction | useEffect(() => { … }, []) | const [utilisateurs, setUtilisateurs] = useState([]) |
| classe | componentDidMount() { … } | this.state = { utilisateurs: [] } + this.setState |
Leçon 5
Leçon 6
1React possède-t-il une API intégrée pour Ajax ?
2Avec axios, les données de la réponse sont dans…
3Avec fetch, pour obtenir les données JSON, on appelle…
4Dans un composant fonction, l'appel à l'API se place dans…
5Dans un composant classe, l'appel à l'API se place dans…
6useEffect(f, [id]) exécute f…
7La fonction passée directement à useEffect peut-elle être async ?
L'essentiel du chapitre
fetch() (intégré) ou axios (npm install axios).axios.get(url).then(res => res.data) ; fetch : fetch(url).then(r => r.json()).then(data => …) ; ou async / await dans une fonction getData interne.useEffect(() => {…}, []) (une fois) ou [id] (à chaque changement) ; résultat dans un état (useState([])). Oublier [] → requêtes sans fin.componentDidMount(), résultat avec this.setState.données.map(x => <div key={x.id}>…</div>) ; objets imbriqués : user.address.city./users, /posts, /comments, /albums, /photos, /todos, /users/3, filtres ?userId=1.Vidéo · À regarder
FRApprendre le JavaScript : Promise
Grafikart · YouTube
Apprendre React en 1 heure - Cours complet débutant
Comment Coder · YouTube
Le tableau des endpoints du support répète deux fois /comments et deux fois /todos. Les objets {userId, id, title} viennent en réalité de /albums, et il manque /posts (utilisé dans le TP). Le tableau ci-dessus est corrigé.
Les exemples du cours sont faits à la fois avec fetch et avec axios.
axios est un client HTTP basé sur les promesses, pour le navigateur et Node.js : simple, facile à étendre, et très léger.
npm install axiosimport axios from 'axios'
axios.get('https://jsonplaceholder.typicode.com/users').then(
(res)=>{ console.log(res)}
)La console affiche un objet avec les propriétés data, status, statusText, headers… La propriété data contient le tableau des utilisateurs :
axios.get('https://jsonplaceholder.typicode.com/users').then(
(res)=>{ console.log(res.data)} // un Array de 10 users
)Où placer le code d'appel ?
Dans un composant fonction, l'appel se place dans useEffect, avec [] en deuxième argument pour qu'il s'exécute une seule fois, au premier rendu :
useEffect(()=>{
axios.get('https://jsonplaceholder.typicode.com/users').then(
(res)=>{ console.log(res.data)}
)
},[])Sans [], l'effet s'exécute après chaque rendu. Or l'appel met l'état à jour (setUtilisateurs), ce qui provoque un nouveau rendu, donc un nouvel appel… L'application envoie des requêtes sans fin au serveur.
Afficher le nombre d'utilisateurs
On crée la variable d'état utilisateurs pour garder le résultat de l'API :
import React, { useEffect, useState } from 'react'
import axios from 'axios'
export default function App(){
const [utilisateurs,setUtilisateurs]=useState([])
useEffect(()=>{
axios.get('https://jsonplaceholder.typicode.com/users')
.then((res)=>{ console.log(res.data);setUtilisateurs(res.data)})
},[])
return(
<div>
<h1>nombre d'utilisateurs: {utilisateurs.length}</h1>
</div>
)
}utilisateurs vaut [] → « nombre d'utilisateurs: 0 ».useEffect lance la requête.setUtilisateurs(res.data) → nouveau rendu → « nombre d'utilisateurs: 10 ».Afficher les utilisateurs
import React, { useEffect, useState } from 'react'
import axios from 'axios'
export default function App(){
const [utilisateurs,setUtilisateurs]=useState([])
useEffect(()=>{
axios.get('https://jsonplaceholder.typicode.com/users')
.then((res)=>{setUtilisateurs(res.data)})
},[])
return(
<div>
<h3>liste utilisateurs</h3>
<div>
<h1>nombre d'utilisateurs: {utilisateurs.length}</h1>
{utilisateurs.map(user=>{
return(
<div className='child' key={user.id}>
<h3 style={{color:"rgb(92, 62, 3)"}}>nom: {user.name} {user.username}</h3>
<p>email:{user.email}</p>
<p> ville:{user.address.city} rue:{user.address.street} </p>
</div>
)
})}</div>
</div>
)}.child{
background-color:rgb(158, 227, 133);
width:40%;
margin:4px auto;
padding:10px;
border: 1px solid rgb(70, 88, 64);
border-radius: 4px;
box-shadow: 0 0 8px rgb(210, 217, 208);
}Le CSS du support écrit box-shadow:8px rgb(210, 217, 208); ; : cette valeur est invalide (il faut au moins deux longueurs) et le navigateur l'ignore. box-shadow: 0 0 8px rgb(210, 217, 208); donne l'ombre voulue.
Avec async / await
useEffect(()=>{
const getData=async ()=>{
const users= await axios.get('https://jsonplaceholder.typicode.com/users')
setUtilisateurs(users.data)
}
getData()
},[])La fonction passée à useEffect ne peut pas être async elle-même : on crée une fonction async à l'intérieur (getData) et on l'appelle tout de suite.
fetch('https://jsonplaceholder.typicode.com/users')
.then((response)=>{ console.log(response); return response.json()})
.then((users)=>{console.log(users);setUtilisateurs(users)})La première réponse (response) n'est pas encore du JSON utilisable : on lui applique response.json(), qui renvoie une promesse, d'où le deuxième .then(). users contient alors le tableau des utilisateurs, qu'on met dans l'état avec setUtilisateurs(users).
const [utilisateurs,setUtilisateurs]=useState([])
useEffect(()=>{
const getData=async ()=>{
const response= await fetch('https://jsonplaceholder.typicode.com/users')
const users=await response.json()
setUtilisateurs(users)
}
getData()
},[])Dans un composant classe, le code d'appel se place dans componentDidMount(), exécutée une fois après le premier rendu (chapitre 7).
import React from "react";
import axios from 'axios'
export default class App extends React.Component{
constructor(props) {
super(props)
this.state = {
utilisateurs:[]
}
}
componentDidMount(){
axios.get('https://jsonplaceholder.typicode.com/users')
.then((res)=>{this.setState({utilisateurs:res.data})})
}
render(){
return(
<div>
<h3>liste utilisateurs</h3>
<div>
<h1>nombre d'utilisateurs: {this.state.utilisateurs.length}</h1>
{this.state.utilisateurs.map(user=>{
return(
<div className='child' key={user.id}>
<h3 style={{color:"rgb(92, 62, 3)"}}>nom: {user.name} {user.username}</h3>
<p>email:{user.email}</p>
<p> ville:{user.address.city} rue:{user.address.street} </p>
</div>
)
})}</div>
</div>
)
}
}Avec fetch, on change seulement componentDidMount :
componentDidMount(){
fetch('https://jsonplaceholder.typicode.com/users')
.then((response)=>{ console.log(response); return response.json()})
.then((users)=>{this.setState({utilisateurs:users})})
}L'endpoint https://jsonplaceholder.typicode.com/users/3 renvoie l'utilisateur d'id 3 :
{
"id": 3,
"name": "Clementine Bauch",
"username": "Samantha",
"email": "Nathan@yesenia.net",
"address": { "street": "Douglas Extension", "suite": "Suite 847", "city": "McKenziehaven",
"zipcode": "59590-4157", "geo": { "lat": "-68.6102", "lng": "-47.0653" } },
"phone": "1-463-123-4447",
"website": "ramiro.info",
"company": { "name": "Romaguera-Jacobson", "catchPhrase": "Face to face bifurcated interface",
"bs": "e-enable strategic applications" }
}L'utilisateur saisit un id ; les informations de l'utilisateur s'affichent à chaque changement. Si le champ est vide ou si l'id n'existe pas, le message « svp choisir un id valide !!!! » s'affiche.
import React, { useEffect, useState } from 'react'
export default function App(){
const [id,setId]=useState(1)
const [utilisateur,setUtilisateur]=useState({})
const [address,setAddress]=useState({})
function handelChangeId(event){
setId(event.target.value)
}
useEffect(
()=>{
fetch(`https://jsonplaceholder.typicode.com/users/${id}`)
.then((response)=>{ return response.json()})
.then((user)=>{setAddress(user.address);setUtilisateur(user);})
},[id] )
return(
<div>
<h1>Details utilisateur</h1>
<div>
<label>donner le id:</label>
<input type="text" onChange={handelChangeId} value={id}></input>
</div>
{utilisateur && address ?
<div className='child' key={utilisateur.id}>
<h3 style={{color:"rgb(92, 62, 3)"}}> id:{utilisateur.id} nom: {utilisateur.name} {utilisateur.username}</h3>
<p>email:{utilisateur.email}</p>
<p> telephone:{utilisateur.phone} </p>
<p> site web:{utilisateur.website} </p>
<p> rue : {address.street} </p>
<p> ville : {address.city} </p>
</div>:"svp choisir un id valide!!!!" }
</div>
)
}[id] : cette fois, le tableau de dépendances contient id : l'effet se relance à chaque changement de l'id ;`…/users/${id}` ;/users/99), l'API renvoie {} : user.address est undefined, la condition utilisateur && address est fausse et le message s'affiche.On utilise deux endpoints : /users et /posts. Un utilisateur peut avoir plusieurs posts : un post appartient à l'utilisateur dont l'id est égal au userId du post. L'application affiche les utilisateurs ; un clic sur le bouton « details posts » d'un utilisateur affiche ses posts.
import React, { useEffect, useState } from "react";
import axios from "axios";
import ListUser from "./components/ListUser";
export default function App() {
const [utilisateurs, setUtilisateurs] = useState([]);
useEffect(() => {
const getData = async () => {
const users = await axios.get("https://jsonplaceholder.typicode.com/users");
setUtilisateurs(users.data);
};
getData();
}, []);
return (
<div>
{utilisateurs?
(<div>
<ListUser utilisateurs={utilisateurs} />
</div>): "pas d'utilisateurs!!!!"}
</div>
);
}import React, { useState } from "react";
import User from "./User";
export default function ListUser(props){
const [activeId,setActiveId]=useState(1)
const users=props.utilisateurs
function handleChangeActiveId(id){
setActiveId(id)
}
return(<div className="App" >
<h1>nombre d'utilisateurs: {props.utilisateurs.length}</h1>
{users.map(user=>{
return(<User user={user} activeId={activeId}
handleChangeActiveId={handleChangeActiveId}/>)
})}
</div>)
}import React, { useState } from "react";
import axios from 'axios'
export default function User(props){
const [posts,setPosts]=useState([])
function HandelPosts(){
const getData = async () => {
const posts = await axios.get("https://jsonplaceholder.typicode.com/posts");
setPosts(posts.data.filter((post)=>post.userId==props.user.id));
};
getData();
props.handleChangeActiveId(props.user.id)
}
return(<div className="child" key={props.user.id}>
<h3>nom: {props.user.name} {props.user.username}</h3>
<p>email:{props.user.email}</p>
<p> ville:{props.user.address.city} rue:{props.user.address.street} </p>
<button onClick={HandelPosts}>details posts</button>
{props.activeId==props.user.id &&(
<div>
<h5 style={{color:"green"}}>nombre des posts: {posts.length}</h5>
{posts.map(post=>{
return(<div className="post" key={post.id}>
<h5>{post.title}</h5>
<p>{post.body}</p>
</div>)
})}
</div>
)}
</div>)
}.App{
background-color:rgb(143, 203, 122);
width:90%;
margin:0 auto;
padding:10px;
border: 1px solid rgb(96, 119, 88);
border-radius: 4px;
}
.child{
background-color:rgb(158, 227, 133);
width:60%;
margin:4px auto;
padding:10px;
border: 1px solid rgb(70, 88, 64);
border-radius: 4px;
}
.post{
background-color:rgb(244, 190, 102);
width:90%;
margin:2px auto;
padding:4px;
border: 1px solid rgb(70, 88, 64);
border-radius: 2px;
}Comment ça marche ?
App charge les utilisateurs une fois (useEffect + []) et les passe à ListUser.ListUser garde activeId : l'utilisateur dont les posts sont ouverts. Il passe à chaque User l'activeId et la fonction handleChangeActiveId (communication enfant → parent, chapitre 7).User charge les posts, garde ceux de son utilisateur (filter sur userId) et prévient le parent : props.handleChangeActiveId(props.user.id).User dont l'id est égal à activeId affiche ses posts : ouvrir un utilisateur referme l'autre.Dans ListUser, le <User …/> créé dans map n'a pas de key : la console affiche l'avertissement des clés. La key={props.user.id} mise dans User.js ne sert à rien (React la veut sur l'élément créé par map). Écris : <User key={user.id} user={user} … />. Autre amélioration : l'API sait filtrer elle-même : axios.get("https://jsonplaceholder.typicode.com/posts?userId=" + props.user.id) ne télécharge que les posts utiles.
Réalise le TP de la section 6, corrige la key manquante, puis utilise l'adresse filtrée /posts?userId=….
Avec l'endpoint /todos?userId=1, affiche les tâches de l'utilisateur 1 : chaque tâche avec une case cochée si completed vaut true. Affiche en haut « x tâches terminées sur y ». Fais-le avec fetch et async/await.
Reprends le code final de la section 2 (axios). Ajoute deux états : loading (affiche « Chargement… » pendant la requête) et erreur (affiche « Impossible de charger les utilisateurs » si la requête échoue, avec try … catch). Teste l'erreur en changeant l'adresse en /userss.
Ce que tu vas voir (8 min) : le chapitre « useEffect expliqué » : le code placé dans useEffect s'exécute après l'affichage ; avec [], une seule fois. C'est l'endroit où l'on place l'appel à l'API.
Ce que tu vas voir : les promesses : une valeur qui arrivera plus tard, .then() pour utiliser le résultat, .catch() pour l'erreur, puis l'écriture async / await, plus lisible. axios et fetch renvoient des promesses : c'est la base de axios.get(...).then(...) et de await axios.get(...).
Ce que tu vas voir (6 min) : le chapitre « Appeler une API avec fetch » : fetch dans useEffect, conversion avec response.json(), puis les données mises dans l'état et affichées avec map. C'est la section 3.
Crée un composant classe Galerie qui charge /photos?albumId=1 dans componentDidMount (avec axios) et affiche les vignettes (thumbnailUrl) dans une grille, avec le titre sous chaque image.