📍 Article principal du parcours : React Native et Expo : créer une application mobile en 2026
Cette leçon fait partie de la série React Native. Pour la vue d’ensemble, commencez par le guide principal.
Du code à l’interface
Une application mobile, c’est avant tout un écran qui réagit. En React Native, on ne manipule ni div ni span : on assemble des composants natifs traduits en vues Android et iOS réelles. La grille de produits que vos utilisateurs feront défiler, les cartes sur lesquelles ils appuieront, la mise en page qui s’adapte à toutes les tailles d’écran — tout cela se construit avec une poignée de composants de base et un système de style proche du CSS, mais pas identique.
Dans cette leçon, vous transformez l’écran d’accueil vide de StockPoche en une vraie liste d’articles d’inventaire : des cartes affichant le nom du produit, sa quantité en stock et sa photo, défilantes et performantes même avec des centaines d’entrées. Vous apprendrez à styliser proprement, à rendre les éléments tactiles, et à afficher une longue liste sans faire ramer le téléphone.
🎯 Ce que vous allez apprendre
- Utiliser les composants fondamentaux :
View,Text,Pressable,Image. - Styliser avec
StyleSheet.createet comprendre le modèle Flexbox de React Native. - Afficher une liste performante avec
FlatListplutôt qu’une boucle naïve. - Rendre une carte tactile et donner un retour visuel au toucher.
- Extraire un composant réutilisable et le typer avec TypeScript.
🛠️ Ce que vous allez construire
L’écran principal de StockPoche : une liste défilante de cartes-produit. Chaque carte montre le nom de l’article, sa quantité, et une pastille de couleur signalant les stocks bas. À la fin, la liste sera fluide, jolie, et découpée en composants propres prêts à recevoir la navigation dans la leçon suivante.
Prérequis
- Le projet StockPoche créé dans la leçon Démarrer un projet React Native avec Expo.
- Le serveur de développement lancé (
npx expo start) et l’app ouverte dans Expo Go. - Niveau : intermédiaire. Test express — si vous êtes à l’aise avec les fonctions fléchées et le
mapsur un tableau, vous suivrez sans peine. - ⏱️ Temps estimé : ~40 minutes.
Étape 1 — Les briques de base : View et Text
Tout part de deux composants. View est le conteneur — l’équivalent d’une div, mais qui devient une vue native. Text est le seul composant capable d’afficher du texte : en React Native, on ne met jamais du texte directement dans une View, toujours dans un Text. Commençons par poser l’en-tête de l’écran.
// app/(tabs)/index.tsx
import { Text, View } from 'react-native';
export default function Accueil() {
return (
<View>
<Text>Inventaire StockPoche</Text>
<Text>3 articles en stock</Text>
</View>
);
}
À l’enregistrement, l’écran affiche les deux lignes en haut à gauche, collées au bord. C’est volontairement brut : aucun style n’est encore appliqué. Si vous tentez d’écrire du texte hors d’un Text (par exemple directement dans la View), React Native lèvera l’erreur « Text strings must be rendered within a <Text> component » — une des premières erreurs que tout le monde rencontre.
✅ Point d’étape — Les deux lignes de texte s’affichent. Elles sont collées en haut de l’écran, sous l’encoche : c’est normal, on va corriger la mise en page à l’étape suivante.
Étape 2 — Styliser avec StyleSheet
React Native n’utilise pas de fichiers CSS. Les styles sont des objets JavaScript, et on les regroupe avec StyleSheet.create, qui valide les propriétés et optimise leur transmission au moteur natif. Les noms de propriétés sont en camelCase (backgroundColor et non background-color) et les valeurs numériques sont en points indépendants de la densité, sans unité px.
// app/(tabs)/index.tsx
import { StyleSheet, Text, View } from 'react-native';
export default function Accueil() {
return (
<View style={styles.container}>
<Text style={styles.titre}>Inventaire StockPoche</Text>
<Text style={styles.sous}>3 articles en stock</Text>
</View>
);
}
const styles = StyleSheet.create({
container: { flex: 1, padding: 16, paddingTop: 60, backgroundColor: '#f5f5f5' },
titre: { fontSize: 22, fontWeight: 'bold', color: '#1a1a1a' },
sous: { fontSize: 14, color: '#666', marginTop: 4 },
});
L’écran prend tout de suite forme : un fond gris clair, un titre en gras, un sous-titre discret, avec une marge confortable. La propriété flex: 1 sur le conteneur lui fait occuper tout l’espace disponible. Le paddingTop: 60 écarte le contenu de l’encoche ; on verra plus loin une façon plus robuste de gérer les zones de sécurité.
✅ Point d’étape — Vous avez un en-tête stylisé sur fond gris. Si une couleur ne s’applique pas, vérifiez l’orthographe camelCase : une propriété mal nommée est silencieusement ignorée.
Étape 3 — Comprendre Flexbox sur mobile
La disposition en React Native repose entièrement sur Flexbox — le même modèle que sur le web, avec une différence majeure : la direction par défaut est column (de haut en bas), pas row. C’est logique sur un écran de téléphone, plus haut que large. Construisons une ligne d’en-tête où le titre est à gauche et un compteur à droite.
<View style={styles.entete}>
<Text style={styles.titre}>Inventaire</Text>
<Text style={styles.badge}>3</Text>
</View>
entete: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
},
badge: {
backgroundColor: '#0a7ea4', color: 'white',
paddingHorizontal: 10, paddingVertical: 2,
borderRadius: 12, fontSize: 14, fontWeight: 'bold',
},
Avec flexDirection: 'row', les deux textes se placent côte à côte ; justifyContent: 'space-between' les pousse aux extrémités ; alignItems: 'center' les aligne verticalement. Ces trois propriétés règlent 80 % des mises en page mobiles. Retenez la logique : justifyContent agit le long de l’axe principal (celui de flexDirection), alignItems sur l’axe perpendiculaire.
Étape 4 — Préparer les données et la carte produit
Avant d’afficher une liste, il faut des données. Dans une vraie app, elles viendraient d’une API (sujet de la leçon sur l’état et les données) ; ici, on part d’un tableau en dur pour se concentrer sur l’affichage. Définissons un type TypeScript pour un produit, puis quelques exemples réalistes pour notre quincaillerie.
type Produit = {
id: string;
nom: string;
quantite: number;
prix: number; // en FCFA
};
const PRODUITS: Produit[] = [
{ id: '1', nom: 'Vis à bois 4x40 (boîte)', quantite: 24, prix: 2500 },
{ id: '2', nom: 'Marteau arrache-clou', quantite: 3, prix: 4000 },
{ id: '3', nom: 'Rouleau de fil électrique', quantite: 0, prix: 12000 },
];
Le type Produit documente la forme de chaque entrée et vous protège des fautes de frappe : si vous écrivez produit.qantite, l’éditeur le signalera immédiatement. Maintenant, créons un composant CarteProduit réutilisable dans components/. L’isoler dans son propre fichier garde l’écran lisible et permet de le réutiliser ailleurs.
// components/CarteProduit.tsx
import { StyleSheet, Text, View } from 'react-native';
type Props = { nom: string; quantite: number; prix: number };
export function CarteProduit({ nom, quantite, prix }: Props) {
const enRupture = quantite === 0;
return (
<View style={styles.carte}>
<View style={[styles.pastille, { backgroundColor: enRupture ? '#e53935' : '#43a047' }]} />
<View style={{ flex: 1 }}>
<Text style={styles.nom}>{nom}</Text>
<Text style={styles.detail}>{quantite} en stock · {prix} FCFA</Text>
</View>
</View>
);
}
const styles = StyleSheet.create({
carte: {
flexDirection: 'row', alignItems: 'center', gap: 12,
backgroundColor: 'white', padding: 14, borderRadius: 10, marginBottom: 10,
},
pastille: { width: 12, height: 12, borderRadius: 6 },
nom: { fontSize: 16, fontWeight: '600', color: '#1a1a1a' },
detail: { fontSize: 13, color: '#777', marginTop: 2 },
});
Notez le tableau de styles [styles.pastille, { backgroundColor: ... }] : React Native fusionne les styles d’un tableau, ce qui permet de combiner un style fixe et un style calculé. Ici, la pastille vire au rouge quand la quantité tombe à zéro. C’est ainsi qu’on exprime du style conditionnel sans dupliquer.
✅ Point d’étape — Le composant
CarteProduitcompile sans erreur de type. Il ne s’affiche pas encore : on le branche dans la liste à l’étape suivante.
Étape 5 — Afficher la liste avec FlatList
On pourrait afficher les produits avec un simple PRODUITS.map(...), mais ce serait une erreur de performance : tout le contenu serait monté d’un coup, même les éléments hors écran. FlatList résout cela en ne rendant que les éléments visibles et en recyclant les vues au défilement — indispensable dès qu’une liste peut être longue. Branchons-la dans l’écran d’accueil.
// app/(tabs)/index.tsx
import { FlatList, StyleSheet, Text, View } from 'react-native';
import { CarteProduit } from '@/components/CarteProduit';
export default function Accueil() {
return (
<View style={styles.container}>
<Text style={styles.titre}>Inventaire StockPoche</Text>
<FlatList
data={PRODUITS}
keyExtractor={(item) => item.id}
renderItem={({ item }) => (
<CarteProduit nom={item.nom} quantite={item.quantite} prix={item.prix} />
)}
contentContainerStyle={{ paddingVertical: 12 }}
/>
</View>
);
}
Trois propriétés font tout le travail. data reçoit le tableau ; keyExtractor donne à chaque ligne une clé stable (essentiel pour que React suive les éléments) ; renderItem décrit comment afficher une entrée. L’objet reçu par renderItem contient l’élément sous la clé item. À l’écran, les trois cartes apparaissent, avec la pastille rouge sur le rouleau de fil en rupture. Faites défiler : même avec trois éléments, le mécanisme est le même qu’avec trois mille.
✅ Point d’étape — Les trois cartes s’affichent en liste. Si vous voyez l’avertissement jaune « VirtualizedList: missing keys », c’est que
keyExtractorne renvoie pas de valeur unique : vérifiez que chaque produit a bien uniddistinct.
Étape 6 — Rendre les cartes tactiles avec Pressable
Une carte d’inventaire doit pouvoir s’ouvrir au toucher. Le composant Pressable détecte les appuis et, fait précieux, permet de réagir à l’état « pressé » pour donner un retour visuel immédiat — un détail qui sépare une app qui paraît réactive d’une app qui paraît figée. Enveloppons la carte.
import { Pressable } from 'react-native';
<Pressable
onPress={() => console.log('Ouvrir', item.nom)}
style={({ pressed }) => ({ opacity: pressed ? 0.6 : 1 })}
>
<CarteProduit nom={item.nom} quantite={item.quantite} prix={item.prix} />
</Pressable>
La fonction passée à style reçoit un objet { pressed } : on baisse l’opacité pendant l’appui, ce qui assombrit la carte le temps du contact. Pour l’instant, le onPress se contente d’écrire dans la console (visible dans le terminal Metro) ; dans la leçon sur la navigation, il ouvrira la fiche détail du produit. Testez : chaque carte réagit maintenant au toucher.
✅ Point d’étape — Appuyer sur une carte l’assombrit brièvement et écrit son nom dans le terminal. Si rien ne s’affiche dans la console, vérifiez que le terminal
expo startest bien au premier plan.
Étape 7 — Afficher des images avec expo-image
Pour la photo d’un produit, React Native fournit un composant Image, mais Expo propose mieux : expo-image, optimisé pour le cache, les transitions et les formats modernes. Installez-le avec la commande qui choisit automatiquement la version compatible avec votre SDK.
npx expo install expo-image
Utiliser npx expo install plutôt que npm install est important : Expo sélectionne la version de la bibliothèque alignée sur votre SDK, évitant les incompatibilités natives. Ajoutons une vignette à la carte produit.
import { Image } from 'expo-image';
<Image
source={{ uri: 'https://placehold.co/80x80/png' }}
style={{ width: 48, height: 48, borderRadius: 8 }}
contentFit="cover"
transition={200}
/>
La propriété contentFit="cover" recadre l’image pour remplir le cadre sans déformation (l’équivalent de object-fit: cover en CSS), et transition={200} ajoute un fondu de 200 ms à l’apparition. Pour StockPoche, l’URI viendra plus tard de la photo prise avec la caméra ; ici, une image de substitution suffit à valider l’affichage.
🐞 Pièges fréquents
| Symptôme / erreur | Cause probable | Correctif |
|---|---|---|
| « Text strings must be rendered within a <Text> » | Du texte placé directement dans une View |
Envelopper tout texte dans un Text |
| Une couleur ou marge n’a aucun effet | Propriété mal nommée (kebab-case au lieu de camelCase) | backgroundColor, pas background-color |
| Liste qui rame avec beaucoup d’éléments | Utilisation de .map() au lieu de FlatList |
Passer à FlatList ou FlashList |
| Avertissement « missing keys » | keyExtractor absent ou non unique |
Fournir une clé stable et unique par élément |
| L’image ne s’affiche pas | Dimensions non précisées sur une image distante | Toujours donner width et height |
🌍 Réalités du terrain
Sur des téléphones d’entrée de gamme, la différence entre .map() et FlatList n’est pas théorique : une liste de quelques centaines de produits affichée naïvement peut bloquer le défilement pendant une seconde, ce qui suffit à donner une impression d’app « lente ». La virtualisation de FlatList garde l’app fluide même sur du matériel modeste. Pour les listes très longues ou très denses, la bibliothèque FlashList de Shopify (installable via npx expo install @shopify/flash-list) va encore plus loin en recyclant agressivement les vues.
Côté images, soignez le poids : une photo de produit en pleine résolution chargée sur la fiche détail consomme de la donnée à chaque ouverture. expo-image met en cache automatiquement, mais pensez à servir des vignettes redimensionnées dans la liste plutôt que l’image complète — un gain direct sur la facture data de vos utilisateurs et sur la vitesse d’affichage.
✅ Récapitulatif
Vous avez transformé un écran vide en une vraie liste d’inventaire. Vous maîtrisez désormais les composants de base (View, Text, Pressable, Image), le style avec StyleSheet et Flexbox (direction column par défaut, justifyContent/alignItems), l’affichage performant avec FlatList, et l’extraction d’un composant typé réutilisable. L’écran principal de StockPoche est prêt à devenir interactif.
🧾 Aide-mémoire
| Élément | Rôle |
|---|---|
View / Text |
Conteneur / affichage de texte |
StyleSheet.create |
Définir des styles validés et optimisés |
flexDirection: 'row' |
Disposer les enfants horizontalement |
FlatList |
Liste virtualisée performante |
keyExtractor |
Clé unique par élément de liste |
Pressable |
Zone tactile avec état pressed |
npx expo install expo-image |
Composant image optimisé |
💪 À vous de jouer
Ajoutez un style conditionnel sur le nom du produit : quand la quantité est inférieure à 5, affichez le texte de détail en orange pour signaler un stock faible (sans pour autant être en rupture).
Voir une solution
Dans CarteProduit, calculez l’état et appliquez un style en tableau :
const stockFaible = quantite > 0 && quantite < 5;
<Text style={[styles.detail, stockFaible && { color: '#fb8c00' }]}>
{quantite} en stock · {prix} FCFA
</Text>
Le stockFaible && {...} renvoie false quand la condition n’est pas remplie, et React Native ignore les valeurs false dans un tableau de styles — un idiome propre pour du style conditionnel.
Tutoriels frères
- Navigation avec Expo Router — ouvrir la fiche d’un produit au toucher.
- Gérer l’état et consommer une API — remplacer les données en dur par une vraie source.
Pour aller plus loin
- 🔝 Retour au guide principal : React Native et Expo : créer une application mobile
- Documentation officielle : FlatList — React Native
- Documentation officielle : expo-image
FAQ
Q : Puis-je utiliser des classes de style comme avec Tailwind ?
R : Oui, via la bibliothèque NativeWind, qui apporte la syntaxe utilitaire de Tailwind à React Native. Mais comprendre StyleSheet et Flexbox reste indispensable : NativeWind n’est qu’une couche par-dessus.
Q : Quelle différence entre Pressable et TouchableOpacity ?
R : Pressable est le composant moderne recommandé, plus flexible (états pressed, hovered). TouchableOpacity existe toujours et fonctionne, mais Pressable couvre tous ses usages et davantage.
Q : Pourquoi mes marges semblent différentes entre Android et iOS ?
R : Les polices et les hauteurs de ligne par défaut diffèrent légèrement entre plateformes. Pour un rendu identique, fixez explicitement lineHeight et testez sur les deux systèmes — c’est une bonne habitude dès le début.
Q : Faut-il un fichier de styles séparé par composant ?
R : Garder le StyleSheet à la fin du fichier du composant est l’usage le plus courant et le plus lisible. On n’extrait les styles que lorsqu’ils sont réellement partagés entre plusieurs composants.