Skip to Content

Chapitre 3.5 — TextInput & le clavier

Où on en est : afficher, toucher, lister — reste saisir. Drill a peu de formulaires (un champ de recherche de thème, les réglages), mais le clavier virtuel concerne TOUTE app.

⏱️ TL;DRTextInput se contrôle comme un input React (value + onChangeText — sans e.target). Les props mobiles qui changent tout : keyboardType, autoCapitalize, returnKeyType, autoFocus. Le vrai sujet est le clavier virtuel : il recouvre la moitié de l’écran — KeyboardAvoidingView, keyboardShouldPersistTaps et Keyboard.dismiss sont les trois outils de cohabitation.

🎯 Objectifs

  • Contrôler un TextInput et configurer le BON clavier pour chaque champ.
  • Gérer le focus (autoFocus, refs, enchaînement de champs).
  • Empêcher le clavier de masquer l’UI (KeyboardAvoidingView) et le fermer proprement.
  • Construire la barre de recherche de thèmes de Drill.

TextInput contrôlé

import { useState } from 'react' import { StyleSheet, TextInput } from 'react-native' export function ThemeSearch({ onQuery }: { onQuery: (q: string) => void }) { const [query, setQuery] = useState('') return ( <TextInput style={styles.input} value={query} onChangeText={text => { // ← la string directement, pas d'event ! setQuery(text) onQuery(text) }} placeholder="Chercher un thème…" placeholderTextColor="#7a8b94" // ← à styler explicitement autoCapitalize="none" autoCorrect={false} returnKeyType="search" clearButtonMode="while-editing" // iOS ; Android : bouton maison /> ) } const styles = StyleSheet.create({ input: { minHeight: 48, borderRadius: 12, paddingHorizontal: 16, backgroundColor: '#101b21', color: '#e2e8f0', // ← la couleur du TEXTE saisi, souvent oubliée fontSize: 16, }, })

Différences web → RN à encaisser une fois :

  • onChangeText reçoit la string directement (onChange existe aussi, avec un event natif — on ne s’en sert presque jamais).
  • placeholderTextColor est une prop, pas du CSS (pas de pseudo-élément).
  • La couleur du texte saisi se règle dans style.color — l’oublier donne du texte noir sur fond sombre : invisible. Classique absolu du thème dark.

Le bon clavier pour le bon champ

Le clavier virtuel se configure par champ — c’est une courtoisie de base :

ChampProps
RecherchereturnKeyType="search", autoCapitalize="none"
EmailkeyboardType="email-address", autoCapitalize="none", autoComplete="email"
Nombre (durée du timer)keyboardType="number-pad"
Code/pseudoautoCorrect={false}, autoCapitalize="none"
Mot de passesecureTextEntry

returnKeyType change la touche Entrée (search, next, done, send) ; onSubmitEditing réagit à son appui. Pour enchaîner les champs d’un formulaire :

const lastNameRef = useRef<TextInput>(null) <TextInput returnKeyType="next" onSubmitEditing={() => lastNameRef.current?.focus()} /> <TextInput ref={lastNameRef} returnKeyType="done" />

Le clavier, ce voisin envahissant

Quand le clavier monte, il recouvre ~45 % de l’écran. Trois problèmes, trois outils :

1. Il masque le champ ou le bouton Valider → KeyboardAvoidingView.

import { KeyboardAvoidingView, Platform } from 'react-native' <KeyboardAvoidingView behavior={Platform.OS === 'ios' ? 'padding' : 'height'} style={{ flex: 1 }} > {/* le formulaire */} </KeyboardAvoidingView>

Elle compresse ou décale son contenu quand le clavier apparaît. Le behavior optimal diffère par plateforme — le Platform.select ci-dessus est le réglage standard. Sur Android, le manifeste (android:windowSoftInputMode, réglé par Expo à adjustResize) fait déjà une partie du travail : testez toujours AVANT d’empiler les correctifs.

2. Le premier tap sur un bouton ne fait que fermer le clavier → keyboardShouldPersistTaps.

Dans une ScrollView/FlatList contenant un champ (notre recherche au-dessus de la liste de thèmes) : par défaut, clavier ouvert, un tap sur un résultat ferme d’abord le clavier et avale le tap. Le remède :

<FlatList keyboardShouldPersistTaps="handled" />

"handled" : les éléments interactifs reçoivent leur tap même clavier ouvert. À poser systématiquement sur toute liste contenant un champ de saisie.

3. Fermer le clavier à la demande → Keyboard.dismiss().

import { Keyboard } from 'react-native' // Au lancement de la partie, par exemple : Keyboard.dismiss()

Et le pattern « tap dans le vide pour fermer » s’obtient en enveloppant l’écran d’un Pressable sans feedback qui appelle Keyboard.dismiss().

⚠️ Piège — Tester la saisie sur l’émulateur avec le clavier physique du PC cache tous ces problèmes : le clavier virtuel ne monte jamais ! Réflexe : la saisie se teste sur le téléphone (ou en forçant le clavier virtuel de l’émulateur : Settings → désactiver « hardware keyboard input »). Un écran de formulaire jamais testé au vrai clavier virtuel est un écran cassé.

💡 Pour un dev Next.js — Le clavier est LE concept sans équivalent web de ce chapitre : sur le web, le clavier est physique et ne recouvre rien (ou le navigateur mobile gère seul le scroll-into-view). En RN, sa gestion vous incombe. La bonne nouvelle : Drill est un jeu de tap — nos claviers sont rares et nos formulaires courts.

✏️ Exercices

1. Construisez l’écran « Réglages » v0 : un champ numérique « Durée du timer (secondes) » avec le clavier adapté, bornes 5-60 validées à la saisie.

✅ Solution

const [raw, setRaw] = useState('20') const seconds = Number(raw) const valid = Number.isInteger(seconds) && seconds >= 5 && seconds <= 60 <TextInput value={raw} onChangeText={setRaw} keyboardType="number-pad" maxLength={2} style={[styles.input, !valid && styles.inputError]} /> {!valid && <Text style={styles.error}>Entre 5 et 60 secondes</Text>}

On garde la string brute en état (l’utilisateur peut passer par des états invalides en tapant) et on dérive la validité — jamais de Number forcé dans l’état.

2. Votre barre de recherche est au-dessus de la FlatList des thèmes. Clavier ouvert, il faut deux taps pour ouvrir un thème. Diagnostic et correctif ?

✅ Solution

Comportement par défaut : le premier tap ferme le clavier, le second atteint l’item. Correctif : keyboardShouldPersistTaps="handled" sur la FlatList — le premier tap ouvre le thème (et on ajoute Keyboard.dismiss() dans la navigation si on veut fermer en même temps).

3. Pourquoi garder value + onChangeText (contrôlé) plutôt qu’un champ non contrôlé, alors que la doc RN autorise defaultValue ?

✅ Solution

Le contrôlé garde la source de vérité dans React : filtrage de la liste en direct, reset programmatique (setQuery('') du bouton ✕), validation à la frappe, synchronisation avec un store. defaultValue convient aux champs « fire-and-forget » (rare). Même arbitrage que sur le web — avec les mêmes 99 % en faveur du contrôlé.

🧠 Quiz

1. Quelle est la différence entre onChangeText et onChange ?

Réponse

onChangeText reçoit directement la string saisie (l’API du quotidien) ; onChange reçoit un event natif (e.nativeEvent.text) — rarement utile.

2. Trois props pour un champ email confortable ?

Réponse

keyboardType="email-address" (clavier avec @), autoCapitalize="none", autoComplete="email" (+ autoCorrect={false}).

3. À quoi sert keyboardShouldPersistTaps="handled" ?

Réponse

Dans une liste/ScrollView, il permet aux éléments interactifs de recevoir le tap même quand le clavier est ouvert — sinon le premier tap ne fait que fermer le clavier.

4. Quel composant empêche le clavier de recouvrir un formulaire, et avec quel réglage ?

Réponse

KeyboardAvoidingView avec behavior={Platform.OS === 'ios' ? 'padding' : 'height'} — en vérifiant d’abord si l’adjustResize Android ne suffit pas déjà.

5. Pourquoi le texte saisi peut-il être invisible en thème sombre ?

Réponse

La couleur du texte du TextInput ne vient d’aucun héritage : sans color dans son style, le texte reste noir par défaut — noir sur fond sombre. À styler explicitement (tout comme placeholderTextColor).


👉 Chapitre suivant : 3.6 — S’adapter à l’écran — encoches, barres système et diversité des écrans Android.