Rafraîchissez vos connaissances de useState et useEffect
Souvenez-vous… 💭
Le state local est présent à l’intérieur d’un composant : ce composant peut être re-render autant de fois que l'on veut, mais les données seront préservées. Pour cela on utilise useState
, un hook qui permet d'ajouter un state local dans un composant fonction.
useEffect
est également un hook, qui permet d'exécuter des actions après le render de nos composants, en choisissant à quel moment et à quelle fréquence cette action doit être exécutée, avec le tableau de dépendances.
Vous vous en doutez sûrement, nous allons les utiliser pour faire des calls API :
useEffect
nous permettra de déclencher lefetch
;useState
permettra de stocker le retour de l'API dans lestate
.
Alors, mettons-nous au travail. 💪
Récupérez les données d'une API
Reprenez les bases des calls API
Les données sont au cœur d'une application. Qu'il s'agisse de données locales ou bien qu'elles soient récupérées depuis une API, elles viennent alimenter nos composants et nourrir les interactions avec les utilisateurs.
Bon, on en entend parler tout le temps, mais c'est quoi une API ?
Une API (Application Programming Interface) est littéralement une interface de programmation d’application : c'est un moyen de communication entre deux logiciels. Concrètement, pour nous, c'est ce qui nous permet de récupérer des données. Si vous voulez en savoir plus sur les API, je vous conseille l'excellent cours Adoptez les API REST pour vos projets web.
Mais pourquoi on n'a pas mis les données directement dans le front ? On l'a bien fait dans le premier cours et ça fonctionne !
Eh bien... oui, on aurait pu. Ici, on dispose de toutes les données. Mais dans les faits, ce ne sera pas toujours le cas, loin de là. Vous pouvez par exemple avoir besoin que votre contenu soit administré par une personne qui ne sait pas coder. Dans ce cas, vous pourriez utiliser un CMS (comme WordPress, Ghost, etc.), et récupérer le contenu.
Ou bien tout simplement, vous pouvez créer une application complexe qui requiert un système d'authentification, qui sauvegarde des données utilisateurs, etc. Dans ce cas, une application frontend ne suffit pas et doit être complémentaire avec l'application backend.
Mettons tout ça en application dès maintenant !
Pour notre projet pour l'agence Shiny, nous allons utiliser l'API que vous trouverez ici. Vous trouverez toutes les instructions nécessaires pour la faire tourner dans le README.md
. Je vous laisse un petit moment pour cloner le repo et lancer l'API en local. ⏱
... C'est bon, vous avez bien l'API qui tourne en local ? On va aller récupérer le contenu de nos questions sur l'API sur la routehttp://localhost:8000
avec la méthode fetch()
.
fetch()
est la méthode native pour faire des calls API. Nous aurions très bien pu utiliser des outils tels que axios
, mais ici la méthode native a été privilégiée, pour vous éviter une nouvelle installation d'outil externe.
Développez le questionnaire avec les données
Pour nous atteler à l'utilisation de l'API, nous allons développer la page /survey
. Souvenez-vous, dans le chapitre précédent, nous avions créé des liens pour naviguer entre les questions, et rediriger l'utilisateur sur /results
quand il atteignait la dixième question. Eh bien, ici, nous allons continuer à développer cette page afin de récupérer les données depuis l'API. J’ai ajouté un peu de style supplémentaire pour y voir un peu plus clair (que vous pouvez récupérer sur le repo GitHub de ce cours, sur la branche P2C1-exercice).
L’API nous renvoie l’ensemble des questions sur l’endpointhttp://localhost:8000/survey
.
Hé, mais comment on sait ça ?
Bon, c'est facile pour moi parce que j'ai aussi écrit l'API que nous utilisons. Mais vous pouvez tout simplement utiliser la documentation de l'API. Ici, elle est accessible dans le fichier README.md
.
On peut donc l’appeler, comme nous l’avons dit, dans notre useEffect
pour récupérer les questions. Si vous regardez la documentation, vous verrez que la route correspondant aux questions ( http://localhost:8000/survey
) est une route GET
, et qu'elle ne requiert pas de paramètre : on pourra récupérer les données en faisantfetch('http://localhost:8000/survey')
.
Ici, on a donc uniquement besoin d’appeler l’API à la première initialisation de notre composant, et on précise un tableau de dépendances vide dans notre fichier :
useEffect(() => {
fetch(`http://localhost:8000/survey`)
.then((response) => response.json()
.then(({ surveyData }) => console.log(surveyData))
.catch((error) => console.log(error))
)
}, [])
Et voilà : on a bien ce qu’on voulait ! 🤩
Ici, nous avons utilisé des Promises
. Une autre syntaxe aurait été possible avec des async
/ await
. Mais attention, il y a une petite subtilité avec useEffect
. C'est ce que vous verrez dans le screencast à la fin de ce chapitre.
Bon, ce n’est pas tout d’afficher le retour de notre API dans la console : on veut que ce soit visible dans notre application !
Pour cela, nous allons utiliser le state. À l’aide de useState
, on crée donc :
const [questions, setQuestions] = useState({})
questions
va nous permettre de stocker l’objet qui a été retourné par l’API. À partir de là, on peut exploiter questions
assez simplement en appelant :
setQuestions(surveyData)
.
Ici, vous avez pu voir dans votre console que surveyData
est un objet ayant pour clé des nombres. C’est très pratique pour s’assurer que les questions sont toujours ordonnées, et on peut tout simplement accéder à une question avec :
surveyData[questionNumber]
De la même manière, pour savoir s’il faut mettre un lien vers le numéro de question suivant, ou bien un lien vers les résultats, vous pouvez tout simplement vérifier ce que donne l’affirmation :
surveyData[questionNumberInt + 1] ?
Ce qui nous donne le code suivant :
function Survey() {
const { questionNumber } = useParams()
const questionNumberInt = parseInt(questionNumber)
const prevQuestionNumber = questionNumberInt === 1 ? 1 : questionNumberInt - 1
const nextQuestionNumber = questionNumberInt + 1
const [surveyData, setSurveyData] = useState({})
useEffect(() => {
setDataLoading(true)
fetch(`http://localhost:8000/survey`)
.then((response) => response.json()
.then(({ surveyData }) => console.log(surveyData))
.catch((error) => console.log(error))
)
}, [])
return (
<SurveyContainer>
<QuestionTitle>Question {questionNumber}</QuestionTitle>
<QuestionContent>{surveyData[questionNumber]} </QuestionContent>
<LinkWrapper>
<Link to={`/survey/${prevQuestionNumber}`}>Précédent</Link>
{surveyData[questionNumberInt + 1] ? (
<Link to={`/survey/${nextQuestionNumber}`}>Suivant</Link>
) : (
<Link to="/results">Résultats</Link>
)}
</LinkWrapper>
</SurveyContainer>
)
}
export default Survey
Créez un state loading
C'est pas mal tout ça, n'est-ce pas ? Notre question s'affiche bien :
Mais ça vient d'où ce petit moment de “blanc” ? Comment fait-on pour que ça ressemble plus aux sites professionnels ?
Eh bien, ça correspond tout simplement au temps entre lequel le composant est render (généré) et celui où lequel les données sont chargées. Effectivement, d’un point de vue UI (interface utilisateur), ce n’est pas idéal : l’utilisateur ne comprend pas que les données sont en train d’être chargées, et peut alors penser qu’il y a un problème sur l’application.
Une pratique très répandue consiste à mettre un petit loader pour signifier que les données vont bientôt s’afficher. On pourrait mettre un simple texte “Chargement...”, mais bon, on sait manier le CSS : autant s’amuser avec, non ?
Je vous propose de créer un simple Loader
en CSS, directement dans le fichier utils/Atoms.jsx
. Pour cela, on a également besoin d’importer keyframes
depuis la bibliothèque styled-components
. Ce qui nous donne :
import colors from './colors'
import styled, { keyframes } from 'styled-components'
const rotate = keyframes`
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
`
export const Loader = styled.div`
padding: 10px;
border: 6px solid ${colors.primary};
border-bottom-color: transparent;
border-radius: 22px;
animation: ${rotate} 1s infinite linear;
height: 0;
width: 0;
`
On va maintenant utiliser le state pour afficher notre Loader
. Pour cela, on crée une variable isDataLoading
avec useState
:
const [isDataLoading, setDataLoading] = useState(false)
Dans le useEffect
, on vient modifier notre booléen :
useEffect(() => {
setDataLoading(true)
fetch(`http://localhost:8000/survey`)
.then((response) => response.json())
.then(({ surveyData }) => {
setSurveyData(surveyData)
setDataLoading(false)
})
}, [])
... ce qui nous permet ainsi de conditionner le rendu de notre composant : le Loader
s’affiche tant que les données chargent, et une fois qu’on les a bien, le contenu de la question s’affiche à la place du Loader
.
<SurveyContainer>
<QuestionTitle>Question {questionNumber}</QuestionTitle>
{isDataLoading ? (
<Loader />
) : (
<QuestionContent>{surveyData[questionNumber]}</QuestionContent>
)}
...
</SurveyContainer>
Yaaaaaaaay ! 🎉 On a bien notre contenu qui s’affiche comme on le souhaitait !!
Maintenant que nous avons le comportement que nous souhaitons, profitons-en pour implémenter une syntaxe un peu plus moderne, et pour gérer les erreurs :
C'est le moment de mettre tout ça en pratique. 💪
Exercez-vous
Vous avez réussi à récupérer les données depuis le backend de l’agence Shiny : félicitations à vous ! À vous de jouer pour faire la même chose pour la page des profils freelances. Comme d’habitude, vous trouverez le code nécessaire pour commencer l’exercice sur la branche P2C1-begin
.
Pour cela, vous allez devoir :
Récupérer les profils des freelances sur l’endpoint
/freelances
de l’API. Vous pouvez utiliser la syntaxeasync
/await
ou les.then
.Utiliser le
Loader
lorsque le contenu des profils freelances est en train de charger.Afficher les données dans la page.
Afficher une erreur s'il y a eu un problème.
Vous trouverez la solution de l’exercice sur la branche P2C1-solution. 💡
En résumé
Les calls API peuvent s’effectuer simplement avec les hooks
useEffect
etuseState
:useEffect
permet de déclencher l’appel API ;et
useState
permet de stocker les données qui sont retournées.
Il est tout à fait possible d’utiliser des promesses ou des
async
/await
pour effectuer des calls asynchrones en React.En termes d’UI, il est possible de créer un state
loading
pour afficher une animation de chargement pendant que les données sont en train de charger.
Je vous avais dit dans le premier cours que les hooks useState
et useEffect
étaient particulièrement utiles. Je ne vous avais pas menti : en plus de pouvoir créer des interactions locales, ils permettent même d'interagir avec des services extérieurs ! Si c’est pas la belle vie. 😎
Nous allons maintenant nous plonger un peu plus loin dans le monde des hooks avec useContext
, et en profiter pour découvrir le Contexte, qui va nous permettre de partager simplement nos données entre nos composants. Alors c’est partiii ! 🚀