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;DR —
TextInputse contrôle comme un input React (value+onChangeText— sanse.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,keyboardShouldPersistTapsetKeyboard.dismisssont 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 :
onChangeTextreçoit la string directement (onChangeexiste aussi, avec un event natif — on ne s’en sert presque jamais).placeholderTextColorest 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 :
| Champ | Props |
|---|---|
| Recherche | returnKeyType="search", autoCapitalize="none" |
keyboardType="email-address", autoCapitalize="none", autoComplete="email" | |
| Nombre (durée du timer) | keyboardType="number-pad" |
| Code/pseudo | autoCorrect={false}, autoCapitalize="none" |
| Mot de passe | secureTextEntry |
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.