• 30 heures
  • Moyenne

Ce cours est visible gratuitement en ligne.

course.header.alt.is_video

course.header.alt.is_certifying

J'ai tout compris !

Mis à jour le 09/09/2020

Manipulez le State

Connectez-vous ou inscrivez-vous gratuitement pour bénéficier de toutes les fonctionnalités de ce cours !

Dans ce chapitre, nous allons découvrir et utiliser l'API TMDB (The Movie DataBase) pour récupérer toutes sortes de films. Vous verrez que l'API TMDB est très puissante et surtout qu'elle permet de récupérer les informations des films en français ! Tout le contenu et la richesse de cette API sont créés par une communauté de cinéphiles passionnés. :zorro:

Pour notre application, on souhaite récupérer des films en fonction de la recherche faite par l'utilisateur. Un petit coup d’œil à la documentation de l'API TMDB. À première vue, l'API /search correspond à nos attentes : 

/search - Text based search is the most common way. You provide a query string and we provide the closest match. Searching by text takes into account all original, translated, alternative names and titles.

La recherche fonctionne sur le titre du film et quelle que soit la langue. C'est parfait.

Récupérez un token de l'API TMDB

On va commencer par une étape un peu fastidieuse qui est la récupération d'un token sur l'API, car oui, même si l'API est gratuite, il faut un token pour l'utiliser. Celui-ci se récupère en créant un compte et un projet sur l'API.

Heureusement pour nous, l'obtention de ce token se fait en quelques minutes. Ce n'est pas le meilleur moment de ce cours, mais il faut que l'on passe par là si on souhaite utiliser l'API TMDB.

Première étape, la création d'un compte : https://www.themoviedb.org/account/signup?language=fr

API TMDB Création d'un compte
API TMDB Création d'un compte

Vous allez recevoir un mail "Email Verification Required" pour activer votre compte. Cliquez sur le lien du mail. Vous êtes redirigé vers l'écran de login, connectez-vous avec les identifiants utilisés précédemment pour la création de votre compte.

Tout en haut à droite, vous devriez voir une pastille avec la première lettre de votre nom d'utilisateur (moi, c'est le M) :

API TMDB Pastille compte utilisateur
API TMDB Pastille compte utilisateur

Cliquez sur la pastille, un menu déroulant s'affiche, sélectionnez "Paramètres". Vous arrivez dans l'écran de réglages de votre compte. Dans le menu à gauche, une entrée s'appelle "API", cliquez dessus. Vous devriez voir ceci :

API TMDB : Ecran réglages API
API TMDB : Écran réglages API

Assez logiquement, ici, nous allons cliquer sur le lien dans "Demander une clé API".

On nous demande de choisir entre "Développeur" et "Professionnel" :

API TMDB : Développeur ou professionnel ?
API TMDB : Développeur ou professionnel ?

Choisissez "Développeur". Vous devez ensuite accepter les termes d'utilisation de l'API, ce qui résulte en l'affichage d'un formulaire qui ne fait pas rire du tout. Je vous mets ce que j'ai au début, sur quoi vous pourriez bloquer. Certains champs sont obligatoires, mais aberrants, comme le lien de l'application. Tant pis, on met un truc bidon pour que le formulaire soit validé :

API TMDB : Création d'une application
API TMDB : Création d'une application

Il faut mettre un peu de texte dans "Résumé de l'application", sinon cela ne passe pas. :p

C'est fini, vous arrivez sur un écran où vous retrouvez, tout en bas, le token dont on a besoin :

API TMDB : Récupération du token
API TMDB : Récupération du token

Bravo, vous avez terminé l'étape la plus fastidieuse de ce cours :lol: et vous êtes fin prêt à utiliser toute la puissance de l'API TMDB. Sans plus attendre, je vous propose de mettre en pratique ce token et de récupérer des films en fonction de la recherche effectuée.

Récupérez des films depuis l'API

C'est une très bonne pratique de séparer la logique API du reste de l'application. Pour cela, on va créer un nouveau dossier et un nouveau fichier avec tous nos appels API. Créer un dossier API à la racine de votre projet et, à l'intérieur de celui-ci, un fichier TMDBApi.js.

Créer une constante avec le token récupéré tout à l'heure :

// API/TMDBApi.js
const API_TOKEN = "VOTRE_TOKEN_ICI";

Ensuite, on crée une méthode pour rechercher des films en fonction d'un texte et on constitue l'URL à appeler avec le texte fourni et notre  API_TOKEN  . Pour savoir comment construire l'URL, il faut se référer à la documentation de l'API TMDB

// API/TMDBApi.js
const API_TOKEN = "VOTRE_TOKEN_ICI";
export function getFilmsFromApiWithSearchedText (text) {
const url = 'https://api.themoviedb.org/3/search/movie?api_key=' + API_TOKEN + '&language=fr&query=' + text
}

Il ne nous reste plus qu'à faire l'appel à l'API. React Native vous propose d'utiliser l'API Fetch dans sa documentation pour tous vos appels réseaux. Fetch est très simple à utiliser. On va partir sur cela, mais rien ne vous empêche plus tard d'utiliser d'autres services pour vos appels réseaux : XMLHttpRequest ou encore Axios.

// API/TMDBApi.js
const API_TOKEN = "VOTRE_TOKEN_ICI";
export function getFilmsFromApiWithSearchedText (text) {
const url = 'https://api.themoviedb.org/3/search/movie?api_key=' + API_TOKEN + '&language=fr&query=' + text
return fetch(url)
.then((response) => response.json())
.catch((error) => console.error(error))
}

Ici, on appelle notre URL de recherche avec, en paramètre, le token et le texte recherché. La fonction  then  convertit la réponse de notre API en JSON et la retourne. En cas d'erreur, on passe automatiquement dans le  catch  et on affiche une erreur à l'écran.

Dans notre component custom Search, on va appeler cette méthode au clic sur le bouton "Rechercher". Pour cela, on crée une action sur notre bouton :

// Components/Search.js
class Search extends React.Component {
_loadFilms() {
}
render() {
return (
<View style={styles.main_container}>
<TextInput style={styles.textinput} placeholder='Titre du film'/>
<Button title='Rechercher' onPress={() => this._loadFilms()}/>
<FlatList
data={films}
keyExtractor={(item) => item.id.toString()}
renderItem={({item}) => <FilmItem film={item}/>}
/>
</View>
)
}
}

Revenons à notre action. On importe la méthode  getFilmsFromApiWithSearchedText  du fichier "API/TMDBApi.js" :

// Components/Search.js
import { getFilmsFromApiWithSearchedText } from '../API/TMDBApi' // import { } from ... car c'est un export nommé dans TMDBApi.js

Et enfin, on appelle la méthode au clic sur le bouton "Rechercher" avec un texte par défaut pour commencer. On verra plus tard comment récupérer le texte de notre  TextInput  : 

// Components/Search.js
_loadFilms() {
getFilmsFromApiWithSearchedText("star").then(data => console.log(data));
}

C'est tout bon. Placez-vous sur votre application, mettez votre terminal en évidence. Cliquez sur le bouton "Rechercher", vous devriez voir apparaître plein de logs avec des données de films. Je ne mets pas tout, mais cela ressemble à ça :

13:57:54: Object {
13:57:54:   "page": 1,
13:57:54:   "results": Array [
13:57:54:     Object {
13:57:54:       "adult": false,
13:57:54:       "backdrop_path": "/c4zJK1mowcps3wvdrm31knxhur2.jpg",
13:57:54:       "genre_ids": Array [
13:57:54:         12,
13:57:54:         28,
13:57:54:         878,
13:57:54:       ],
13:57:54:       "id": 11,
13:57:54:       "original_language": "en",
13:57:54:       "original_title": "Star Wars",
...

Bravo ! Vous venez de réaliser votre tout premier appel sur l'API TMDB. :soleil:

Bon, afficher des films dans un terminal, c'est bien. Les afficher dans une application, c'est mieux.

Affichez les films de l'API

Pour afficher les films de l'API, il faut déjà les stocker dans une propriété de notre component. En React, on a pour habitude de définir nos propriétés dans le constructeur du component. Vous n'êtes pas obligé de faire pareil, mais c'est une bonne pratique.

On va donc réécrire le constructeur d'un component. Cela ressemble à ça :

// Components/Search.js
class Search extends React.Component {
constructor(props) {
super(props)
// Ici on va créer les propriétés de notre component custom Search
}
//...
}

Je vous invite à créer une propriété  _films  et à l'initialiser avec un tableau vide pour l'instant. Vous pouvez également supprimer l'import import films from '../Helpers/filmsData' , il ne nous servira plus :

// Components/Search.js
class Search extends React.Component {
constructor(props) {
super(props)
this._films = []
}
//...
}

On va dire à notre liste de films (FlatList) d'utiliser cette propriété pour gérer ses données :

// Components/Search.js
<FlatList
data={this._films}
keyExtractor={(item) => item.id.toString()}
renderItem={({item}) => <FilmItem film={item}/>}
/>

Il ne nous reste plus qu'à modifier notre propriété  _films  avec les films récupérés depuis l'API TMDB et à mettre à jour notre liste de données. Si vous faites attention aux logs que l'on a affichés tout à l'heure lors de l'appel API, vous avez pu voir que les films sont stockés dans un tableau results . On va donc récupérer nos films dans ce tableau :

// Components/Search.js
_loadFilms() {
getFilmsFromApiWithSearchedText("star").then(data => {
this._films = data.results
this.forceUpdate()
})
}

Basculez sur votre application, un reload sera sûrement nécessaire. Cliquez sur le bouton "Rechercher" :

FlatList avec les films récupérés depuis l'API TMDB
FlatList avec les films récupérés depuis l'API TMDB

Yes ! :magicien: Ce n'était pas si compliqué, n'est-ce pas ? Allez-y, scrollez dans la liste des films ! Vous allez retrouver vos films récupérés depuis l'API.

Bon, je suis désolé, je casse un peu l'ambiance, mais en fait, on a fait un truc pas bien du tout ici. On a forcé un component à se re-rendre avec la méthode forceUpdate(). C'est une chose à éviter.

D'accord, mais si on n'appelle plus la méthode  forceUpdate()  , notre component ne va pas se re-rendre et on ne verra jamais notre liste de films se mettre à jour ?

C'est vrai, mais je vais vous montrer une solution beaucoup plus propre et efficace pour re-rendre un component. C'est une solution qui existe depuis longtemps dans le développement web : le State

Le State

Pour vous présenter le state, je vais m'appuyer sur la définition faite par React Native, elle est très parlante.

Différence entre prop et state

Pour contrôler un component, il y a deux types de données. Les props et le state.

Les props (que l'on a déjà vues) sont fixées par le component parent et ne peuvent pas être modifiées par le component qui les reçoit. Par exemple, pour nos items FilmItem, on a défini, depuis le component parent Search, une prop film  : 

renderItem={({item}) => <FilmItem film={item}/>}

Une fois dans le component FilmItem, on peut récupérer le film via  this.props.film, mais on ne peut pas le modifier et afficher un autre film à la place, par exemple. Vous pouvez toujours essayer de faire  this.props.film = {données d'un autre film}  dans le component FilmItem, cela n'aura aucun effet. Les props sont, en langage plus technique, accessibles en lecture uniquement.

Il nous manque donc un élément pour modifier notre component et ses données affichées. C'est là qu'intervient le state.

Dans notre component Search, on gère un tableau de films que l'on affiche ensuite dans une FlatList. Ce tableau de films est modifié à chaque fois que l'on appelle l'API TMDB. C'est l'exemple parfait pour utiliser le state.

Initialisez le state

Comme pour les variables d'un component, on a pour habitude d'initialiser le state dans le constructeur du component. On va donc initialiser notre state avec un tableau de films vide :

// Components/Search.js
constructor(props) {
super(props)
this.state = { films: [] }
}

Ensuite, on va dire à notre FlatList d'utiliser cette nouvelle donnée pour afficher ses films :

// Components/Search.js
<FlatList
data={this.state.films}
keyExtractor={(item) => item.id.toString()}
renderItem={({item}) => <FilmItem film={item}/>}
/>

Il ne nous reste plus qu'à modifier le state avec les films récupérés depuis l'API.

Modifiez le state

Pour modifier la valeur  films  de votre state, je suis sûr que vous avez pensé à faire :this.state.films = data.results . Cela va fonctionner, mais vous serez obligé de faire un  forceUpdate() juste après pour que votre FlatList se mette à jour et affiche vos nouveaux films. C'est exactement ce que nous souhaitons éviter de faire, ce n'est donc pas la bonne solution. :(

En React, pour modifier une donnée du state, on passe toujours par  setState

D'accord, mais pourquoi ? Il fait quoi exactement, ce setter ?

 setState  récupère les modifications de vos données et indique à React que le component a besoin d'être re-rendu avec ces  nouvelles données.

Commençons par enlever ce monstrueux  forceUpdate()  de notre application et utilisons  setState lors de la récupération des films de l'API :

// Components/Search.js
_loadFilms() {
getFilmsFromApiWithSearchedText("star").then(data => {
this.setState({ films: data.results })
})
}

Retournez sur votre application. Rechargez-la si nécessaire. Cliquez sur le bouton "Rechercher" et TADAAM :magicien: :

Mise à jour de la liste de données avec setState
Mise à jour de la liste de données avec setState

 Que s'est-il passé exactement ici ?

Nous avons modifié les données de notre component Search, le state, en passant par la fonction  setState . React a identifié que le state de votre component Search a changé. Il va alors demander à votre component Search de se re-rendre avec le nouveau state. Tout votre component Search est re-rendu et, cette fois,  this.state.films  contient la liste des films de l'API. Votre FlatList utilise ces nouvelles données et les affiche.

Vous ne me croyez pas quand je vous dis que le component est entièrement re-rendu ? Vous avez raison de ne pas me croire sur parole. :lol: Je vous propose une petite démonstration. Ajoutez un log dans la méthode  render  de votre component : 

// Components/Search.js
render() {
console.log("RENDER")
return (
...
)
}

Au lancement de l'application, vous devriez voir :

09:06:02: RENDER

C'est normal, au premier affichage du component, celui-ci est rendu. Au clic sur le bouton "Rechercher", on peut voir :

09:06:02: RENDER
09:06:05: RENDER

Je ne vous ai pas menti. Une fois  setState  appelé, TOUT le component est re-rendu. 

React préconise fortement de n'utiliser que les données provenant des props et du state dans le  render de vos component. Ainsi, vous vous assurez que vos components sont toujours mis à jour dès que leurs informations changent.

Je ne veux plus voir de  forceUpdate()  dans vos applications à présent. Utiliser toujours les props et le state pour mettre à jour vos components.

N'abusez pas du state

Une erreur que l'on commet régulièrement, moi le premier, est d'utiliser le state à tout va pour gérer toutes les données d'un component. Vous allez voir, dans la prochaine fonctionnalité que l'on va mettre en place, que, parfois, utiliser le state pour gérer une donnée de notre component est une mauvaise idée.

Dans notre application, actuellement, on fait toujours la même recherche de films avec "Star". Je veux à présent utiliser le texte saisi dans mon TextInput

Récupérer la valeur d'un Component

Première étape, il nous faut récupérer la valeur de notre TextInput.

Facile. Quand l'utilisateur clique sur le bouton "Rechercher", je récupère la valeur du TextInput, non ?

Oui, pourquoi ne pas faire quelque chose comme :  monTextInput.getText()  ? C'est ce que je cherchais souvent à faire au début de mon apprentissage de React. Mais cela ne fonctionne pas comme ça. :p 
En React, vous devez penser différemment. Si vous souhaitez récupérer la valeur d'un component, il faut qu'un évènement ait lieu sur ce component.

Pour l'instant, le seul évènement qui survient sur notre TextInput, c'est l'évènement  onChangeText  , appelé à chaque fois que l'utilisateur saisit un caractère. Si vous me suivez toujours, cela signifie que, pour être sûr d'avoir la bonne valeur du TextInput, vous devez la récupérer à chaque fois que l'utilisateur saisit un caractère.

Si vous ne récupérez pas la valeur pendant la saisie et que l'utilisateur clique sur le bouton "Rechercher", c'est foutu ! L'évènement appelé par le clic est l'évènement  onPress  du Button. Aucun évènement n'a lieu sur le TextInput à cet instant, vous ne pourrez pas récupérer le texte du TextInput.

Nous allons donc récupérer le texte saisi à chaque caractère saisi et, pour vous montrer pourquoi il ne faut pas stocker n'importe quelle donnée dans le state, on va stocker le texte dans le state :  

// Components/Search.js
class Search extends React.Component {
constructor(props) {
super(props)
this.state = {
films: [],
searchedText: "" // Initialisation de notre donnée searchedText dans le state
}
}
_searchTextInputChanged(text) {
this.setState({ searchedText: text })
}
render() {
console.log("RENDER")
return (
<View style={styles.main_container}>
<TextInput
style={styles.textinput}
placeholder='Titre du film'
onChangeText={(text) => this._searchTextInputChanged(text)}
/>
//...
</View>
)
}
}

Ici, j'ai défini une propriété  searchedText  dans mon state et, à chaque modification du texte du TextInput, j'appelle  setState  pour mettre à jour ma propriété  searchedText  de mon state. 

Maintenant, on va vérifier que tout fonctionne et on va appeler notre recherche de films avec le texte récupéré du TextInput, et uniquement si le texte n'est pas vide :

// Components/Search.js
_loadFilms() {
console.log(this.state.searchedText) // Un log pour vérifier qu'on a bien le texte du TextInput
if (this.state.searchedText.length > 0) { // Seulement si le texte recherché n'est pas vide
getFilmsFromApiWithSearchedText(this.state.searchedText).then(data => {
this.setState({ films: data.results })
})
}
}

Lancez l'application, saisissez du texte dans le TextInput, par exemple "Harry", et cliquez sur le bouton pour faire une recherche.

Dans les logs, on a :

19:05:01 Harry

Cela fonctionne, on récupère bien le bon texte dans le TextInput et côté application :

Texte du TextInput stocké dans le state et lancement d'une recherche
Texte du TextInput stocké dans le state et lancement d'une recherche

Et bien, c'est quoi ton problème, là ? Ça marche super bien !

Vous avez raison, cela marche, mais je vais vous montrer le problème ici. Ajoutez un log tout simple, comme tout à l'heure, dans le  render  de notre component Search :

// Components/Search.js
render() {
console.log("RENDER")
return (
...
)
}

Lancez votre application, vous devriez avoir un premier log montrant que l'on appelle bien le  render  de notre component à son initialisation :

19:10:10 RENDER

Maintenant, saisissez du texte dans le TextInput, comme tout à l'heure "Harry", mais n'appuyez pas sur le bouton "Rechercher"  et regardez bien vos logs. Vous devriez voir ceci :

19:10:10 RENDER
19:10:10 RENDER
19:10:10 RENDER
19:10:10 RENDER
19:10:10 RENDER

 Vous voyez le problème ici ?

On vient de re-rendre notre component Search 5 fois, une fois pour chaque lettre saisie. Et le pire dans tout cela est qu'on l'a re-rendu pour rien. Notre component Search a les mêmes informations affichées qu'avant de le re-rendre 5 fois. On n'a pas ajouté, modifié ou supprimé de films et le rendu reste le même. 

Cette observation me permet de vous introduire une très bonne pratique. Dans le state, on ne gère que des données qui, une fois modifiées, peuvent affecter le rendu de notre component.

  • Nos films modifient l'affichage de notre component, ils ont donc leur place dans le state.

  • Le texte du TextInput ne modifie pas l'affichage de notre component, il n'a pas sa place dans le state.

Re-rendre un component reste un processus lourd, il ne faut pas en abuser. Même si notre application fonctionne très bien actuellement, sur des components plus importants, plus long à re-rendre, vous risquez d'avoir des problèmes de performances.

Ici, on va plutôt gérer le texte de notre TextInput comme une simple variable de notre component :

// Components/Search.js
import React from 'react'
import { StyleSheet, View, TextInput, Button, Text, FlatList } from 'react-native'
import FilmItem from './FilmItem'
import { getFilmsFromApiWithSearchedText } from '../API/TMDBApi'
class Search extends React.Component {
constructor(props) {
super(props)
this.searchedText = "" // Initialisation de notre donnée searchedText en dehors du state
this.state = {
films: []
}
}
_loadFilms() {
if (this.searchedText.length > 0) { // Seulement si le texte recherché n'est pas vide
getFilmsFromApiWithSearchedText(this.searchedText).then(data => {
this.setState({ films: data.results })
})
}
}
_searchTextInputChanged(text) {
this.searchedText = text // Modification du texte recherché à chaque saisie de texte, sans passer par le setState comme avant
}
render() {
console.log("RENDER")
return (
<View style={styles.main_container}>
<TextInput
style={styles.textinput}
placeholder='Titre du film'
onChangeText={(text) => this._searchTextInputChanged(text)}
/>
<Button title='Rechercher' onPress={() => this._loadFilms()}/>
<FlatList
data={this.state.films}
keyExtractor={(item) => item.id.toString()}
renderItem={({item}) => <FilmItem film={item}/>}
/>
</View>
)
}
}
const styles = StyleSheet.create({
main_container: {
flex: 1,
marginTop: 20
},
textinput: {
marginLeft: 5,
marginRight: 5,
height: 50,
borderColor: '#000000',
borderWidth: 1,
paddingLeft: 5
}
})
export default Search

Vous pouvez tester et constater que notre application fonctionne tout aussi bien sans utiliser le texte du TextInput dans le state. Côté logs, on n'a plus que deux appels au render du component Search :

19:14:20 RENDER
19:14:25 RENDER

Un à l'initialisation du component et un lorsqu'on lance une recherche et qu'on affiche de nouveaux films.

On arrive à la fin de ce chapitre et on a plutôt bien avancé dans notre application, non ? À présent, vous êtes capable de faire des appels API, de récupérer des données et les afficher.

Mais surtout, vous savez manipuler et utiliser le state, sans trop en abuser, hein ? :D C'est un concept très important en React. Le state et les props gèrent les données de vos components.

Le super  setState  vous assure de re-rendre vos components dès que c'est nécessaire. Fini les  forceUpdate() . En utilisant  setState  , on laisse React utiliser toute sa puissance pour gérer nos components et, pour l'instant, cela marche plutôt bien. :) 

On a découvert beaucoup de concepts depuis le début de cette partie : components, styles, props, appel réseau et state. À vrai dire, on a tout vu, en tout cas, tous les concepts liés à React et React Native. On va donc faire une petite pause. Au lieu de vous apprendre de nouvelles choses, autour de React Native, on va consolider nos bases et pratiquer sur tout ce qu'on a appris. Cela vous dit ? Et pour commencer, je vous propose d'améliorer notre component Search et d'y ajouter quelques fonctionnalités.

Exemple de certificat de réussite
Exemple de certificat de réussite