Rendu itératif en React Native
Pourquoi le rendu itératif?
L’utilité du rendu itératif en React Native réside dans sa capacité à transformer des données en interface visuelle dynamique. Grâce à la méthode Javascript .map(), on peut afficher automatiquement une série de composants (comme des cartes, des boutons ou des lignes de texte) à partir d’un tableau, sans répéter manuellement le code. Cela permet de créer des listes interactives, des menus personnalisés, des galeries ou une liste de produits dans une application e-commerce, tout en gardant le code concis, évolutif et connecté aux données.
Méthode map() - Rappel
Nous avons déjà eu l'occasion de traiter la
méthode Javascript map(). En guise, de rappel, il s'agit d'une méthode qui itère sur les éléments d'un tableau en exécutant une fonction de callback. Cette fonction accepte comme argument l'élément courant du tableau et retourne un nouvel élément (souvent le même élément du tableau mais qui a été transformé). Le résultat de retour de la
méthode map() est un tableau de même longueur que le tableau d'origine.
A titre d'exemple, imaginons qu'on dispose d'un tableau
tab qui contient les valeurs "javascript","react","react native". On souhaite créer un autre tableau nommé
tab_transforme qui contient les mêmes éléments mais transformés en majuscule. Alors on peut envisager un code qui ressemble à ceci:
const tab = ["javascript","react","react native"]
const tab_transforme = tab.map(
arg => arg.toUpperCase()
)
Notez que la méthode map() peut accueillir un deuxième argument qui représente l'indice de l'élément:
const tab = ["javascript","react","react native"]
const tab_transforme = tab.map(
(arg,indice) => arg.toUpperCase()
)
Dans ce cas, l'argument
indice aura consécutivement les valeurs 0, 1 et 2.
Rendre des composants itératifs
Imaginez maintenant que l'on souhaite afficher les éléments du tableau précédent (après leur conversion en majuscule), mais chacun dans un composant
Text.
Je propose ce code:
import { useState } from 'react';
import { StyleSheet, View, Text } from 'react-native';
export default function App() {
const [tech,setTech]=useState(
["javascript","react","react native"]
)
return (
<View style={styles.container}>
{tech.map(arg =>
<Text>{arg.toUpperCase()}</Text>
)}
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
gap:6,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
}
});
Nous avons rendu un nouveau tableau qui contient les élément du tableau
tech (qui est un state et qui désigne technologies) après les avoir transformés en majuscule et les avoir enveloppés dans le composant
<Text></Text>, ce qui donne ce résultat:
JAVASCRIPT
REACT
REACT NATIVE
La key prop pour identifier les éléments d'une liste
Vous avez certainement eu une erreur en exécutant le code précédent. Le message d'erreur ressemble généralement à ceci:
ERROR: Each child in a list should have a unique "key" prop.
En effet, la prop
key qui a provoqué l'erreur, permet à React Native d’identifier de manière unique chaque élément d’une liste (rendu itératif) pour optimiser le rendu. Sans prop key, React ne peut pas savoir quel élément a changé, été ajouté ou supprimé, ce qui pourrait nuire aux performances de l'application ainsi qu'à la stabilité visuelle du rendu.
Il faut que la valeur de la prop key soit unique. Dans ce cas, nous allons juste exploiter le deuxième argument de la méthode map() et qui fait office de l'indice de l'élément du tableau (qui est unique). Donc, le code correct ressemblera à ceci:
import { useState } from 'react';
import { StyleSheet, View, Text } from 'react-native';
export default function App() {
const [tech,setTech]=useState(
["javascript","react","react native"]
)
return (
<View style={styles.container}>
{tech.map((arg,i) =>
<Text key={i}>{arg.toUpperCase()}</Text>
)}
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
gap:6,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
}
});
Utiliser l’index du tableau comme clé (key={i}) peut suffire si la liste est statique, mais devient risqué si elle change (ajout, suppression, tri), car cela peut provoquer des incohérences de rendu. Dans un tel cas, il faut utiliser une valeur unique provenant d'autre source (généralement l'ID de l'élément qui sert de clé primaire dans une table SQL, si on considère que l'on souhaite afficher des éléments provenant d'une base de données).
Exemple - Créer un logger
Un logger (ou système de journalisation) sert à enregistrer, structurer et gérer les messages produits par une application pendant son exécution. Ces messages (appelés logs) permettent de suivre le comportement du système, de diagnostiquer des erreurs ou d’analyser des performances. Un tel système est également utilisé dans les applications multi-utilisateurs, où plusieurs personnes peuvent accéder aux fonctionnalités partagées de l'application (comme l'authentification, l'ajout, la suppression...). Un logger dans ce cas, permet d'enregistrer toutes ces actions afin que l'administrateur puisse savoir qui a fait quoi et quand.
Dans cet exemple, nous allons nous contenter de logger les dates. En effet, nous allons intégrer un bouton qui, une fois pressé, ajoutera la date courante dans un tableau (state). Les valeurs de ce tableaux seront ensuite affichées à l'écran. Nous allons également prévoir un bouton qui efface les logs.
Les logs de cet exemple ne seront pas stockés en dur, mais seulement durant l'exécution de l'application. L'objectif est de voir comment manipuler les tableaux en tant que state et comment réussir le rendu itératif en affichant les dates dans des composants Text.
Je propose le code suivant:
import { useState } from 'react';
import { StyleSheet, Text, View, Button } from 'react-native';
export default function App() {
const [dates,setDates]=useState([])
const handleDates=()=>{
const d=new Date()
setDates([...dates,d.toString()])
}
const handleInit=()=>{
setDates([])
}
return (
<View style={styles.container}>
<View style={{flexDirection:"row", gap:10}}>
<Button title="Ajouter un LOG"
onPress={handleDates} />
<Button title="Réinitialiser"
onPress={handleInit} />
</View>
{dates.map(
(arg,i) =>
<Text key={i}>
{i+1} - {arg}
</Text>
)}
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
gap:6,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
},
});
Je pense que le code est clair est rejoint l'idée de l'exemple que nous avons vu au début de cette leçon. Cependant, il y a une subtilité qu'il faut clarifier, il s'agit de la manipulation des tableaux en tant que state.
Spread operator en Javascript: Opérateur de décomposition
Le
spread operator est un opérateur (ou carrément un mécanisme) qui permet de décomposer un tableau en élément individuels. Il utilise le symbole trois points (...) qui précédent le tableau à décomposer.
Par exemple, si on considère le tableau tab défini comme ceci:
const tab=[10,20,30]
Le fait de le décomposer:
...tab
permet d'avoir ses éléments séparément (10, 20 et 30).
Cela permet de manipuler les tableaux d'une autre manière encore plus robuste. A titre d'exemple, au lieu d'ajouter la valeur 40 à notre tableau en utilisant la syntaxe classique:
tab.push(40)
on peut exploiter le spread operator ce cette façon:
tab=[...tab , 40]
En effet, nous avons décomposé notre tableau puis on a ajouté 40 (en espaçant par une virgule). On a ensuite tout mis entre crochets [] afin de reconvertir ces éléments décomposé en un seul tableau.
Le spread operator est également applicable aux objets Javscript.
Utilité du spread operator en React et React Native
Nous avons vu qu'un state peut être modifié uniquement par le biais de son setter. Donc, si notre setter est un tableau, alors on ne peut pas lui appliquer la méthode push(). Par contre, avec le spread operator on peut facilement faire:
setterDuState([...state , nouvel_élément])
Donc, en revenant à notre exemple, vous avez peut être identifier ceci dans cette instruction:
setDates([...dates,d.toString()])
L’appel à dates.toString() convertit l’objet Date en une chaîne de caractères lisible.