Changement persistant de thème dans React Native à l’aide de Context et de Hooks
Créez un commutateur de thème pour React Native en utilisant Context, useState et useEffect : navigation par onglets, sélection du thème, persistance avec AsyncStorage et indicateur de chargement lors du démarrage.
Le changement de thème semble être un problème de mise en forme, mais il s’agit en réalité d’un problème lié à l’état : le thème actuel doit être lisible depuis n’importe quel écran, pouvoir être modifié depuis l’un d’eux et persister après la redémarrage de l’application. Cela en fait un excellent exercice pour apprendre les Hooks avec quelque chose de plus réaliste qu’un compteur. Cette démarche guide la création d’une petite application React Native dotée de deux onglets, d’un sélecteur de thème et d’un stockage persistant, en utilisant useContext, useState et useEffect pour remplacer ce qui nécessitait auparavant des composants de classe et une quantité importante de code générique.
Pourquoi les Hooks conviennent à ce problème
Les Hooks sont arrivés avec React 16.8, et React Native a obtenu un support stable à partir de la version 0.59. Avant eux, partager un thème signifiait utiliser un fournisseur basé sur des classes pour gérer l’état, des méthodes de cycle de vie pour charger les préférences enregistrées, ainsi que des props de rendu ou des consommateurs dispersés dans l’arborescence. Avec les Hooks, le fournisseur devient un composant fonctionnel simple, l’état est géré par useState, le chargement s’effectue via useEffect, et les consommateurs lisent la valeur avec useContext. La logique se trouve ainsi dans un seul petit fichier au lieu d’être répartie entre différentes méthodes de cycle de vie.
Les versions mentionnées ici indiquent quand cette approche est devenue possible pour la première fois ; les versions actuelles de React Native prennent en charge nativement les Hooks, donc n’importe quel projet récent fonctionnera.
Mise en place du projet
Vous avez besoin d’un projet React Native à la version 0.59 ou ultérieure. Un projet vierge peut être généré à l’aide de react-native init RNThemeProvider (les outils plus récents utilisent plutôt la CLI communautaire ou Expo ; consultez les documents d’intégration actuels).
Deux bibliothèques supplémentaires sont nécessaires :
- react-navigation permet la navigation par onglets.
- AsyncStorage stocke le thème sélectionné sur le dispositif. Il a été retiré du noyau de React Native et est désormais fourni en tant que package communautaire distinct (actuellement publié sous le nom de
@react-native-async-storage/async-storage), il doit donc être installé séparément.
Au sein des versions anciennes de React Native, les modules natifs devaient également être liés manuellement après installation ; avec le lien automatique des versions modernes, cette étape est généralement inutile.
Organisation des fichiers
Une structure petite et prévisible permet de séparer la logique des thèmes des écrans et de la navigation :
- src
— components
— TabBar.js # custom bottom tabbar component
— core
— themeProvider.js # custom hook for theming
— themes.json # JSON array containing our themes
— screens
— Main.js # first tab
— Settings.js # second tab
— App.js # navigation part
— index.js # entry point for react-native
— package.json # dependencies
Le dossier core contient tout ce qui concerne les thèmes : themes.json avec les définitions des thèmes et themeProvider.js avec le contexte, le fournisseur et les outils d’aide. Les écrans se trouvent dans screens, la barre d’onglets personnalisée dans components, et App.js gère la navigation.
Construction du noyau de navigation
Commencez sans aucun thème. Dans App.js, créez un navigateur d’onglets en bas de l’écran avec deux onglets : Main pour le contenu de l’application et Settings pour les préférences. Chaque onglet fait référence à un composant fonctionnel simple dans Main.js et Settings.js qui, pour l’instant, affiche uniquement du texte de remplacement.
Exécutez l’application à ce stade. Si deux onglets apparaissent et que vous pouvez passer d’un onglet à l’autre, le squelette est prêt et chaque étape ultérieure ne fait qu’ajouter du comportement. Les API de navigation ont changé au fil des principales versions de react-navigation, il convient donc de suivre les instructions d’installation correspondant à la version que vous installez plutôt que de copier littéralement des exemples plus anciens.
Définition des thèmes et de l’interface de sélection
Les thèmes en tant que données
Chaque thème est un objet dans themes.json contenant trois champs : une clé unique qui le identifie, une couleur d’arrière-plan et une couleur de texte. En conservant les thèmes sous forme de JSON plutôt que de code, il devient facile de les étendre ; un générateur de palettes comme Coolors permet de trouver rapidement des paires de couleurs qui s’accordent bien.
themeProvider.js importe ce fichier et exporte pour l’instant deux éléments : l’ensemble complet des thèmes affichés par l’écran des paramètres, ainsi qu’un thème par défaut (la deuxième entrée de l’array). La logique relative au fournisseur sera ajoutée ultérieurement.
Les écrans et la barre des onglets
L’écran Settings utilise un FlatList qui affiche une ligne par thème, le titre étant stylisé en fonction du thème actif. L’écran Main applique également le thème actuel à son arrière-plan et à son texte.
Finalement, la barre des onglets doit également refléter le thème choisi. Un composant TabBar personnalisé situé dans components/TabBar.js affiche les onglets et utilise la couleur du thème pour l’onglet actif. Ce composant est enregistré dans le navigateur d’onglets de App.js via les options du navigateur afin d’utiliser un composant de barre des onglets personnalisé.
À ce stade, l’application a un thème, mais uniquement avec celui par défaut préprogrammé. Rien ne réagit encore aux touches. C’est là que les Hooks interviennent.
Partager le thème avec useContext
React Context permet à une valeur de circuler dans l’arborescence sans avoir à transmettre des props à chaque niveau. Si le concept de Context est nouveau pour vous, la documentation React Context explique ce mécanisme.
Dans themeProvider.js, créez un contexte de thème ainsi qu’un composant fournisseur, ThemeContextProvider. Enveloppez le navigateur dans App.js avec ce fournisseur afin que chaque écran et la barre des onglets se trouvent sous lui.
Afin de faciliter l’accès au contexte, ajoutez un composant d’ordre supérieur withTheme dans le même fichier. Ce dernier lit le contexte à l’aide de useContext et transmet le thème au composant encastré sous forme de propriété. Mettez à jour Main, Settings et TabBar pour qu’ils soient exportés via withTheme ; ainsi, ils reçoivent le thème actuel sans savoir d’où il provient. Le guide sur les composants d’ordre supérieur explique ce pattern en plus de détail.
A HOC fonctionne bien lorsque les composants s’attendent déjà à des props. Dans une base de code axée sur les hooks, un petit hook useTheme qui renvoie useContext(ThemeContext) est souvent plus simple, évite une couche d’encapsulation supplémentaire et rend la dépendance visible à l’intérieur du composant. Notre guide sur les hooks personnalisés et la réutilisation de la logique explique pourquoi les hooks partagent la logique plutôt que l’état, ce qui est précisément la raison pour laquelle le contexte reste nécessaire ici.
Changer de thème avec useState
Maintenant, faisons fonctionner le sélecteur. À l’intérieur de ThemeContextProvider, stockez le thème actuel dans useState, initialisé avec le thème par défaut. Insérez à la fois le thème et une fonction setTheme dans la valeur du contexte.
Dans l’écran des Paramètres, appelez setTheme lorsque l’on clique sur une ligne dans le FlatList. Comme l’état du fournisseur change, chaque composant qui lit le contexte se rérend avec de nouvelles couleurs : les écrans et la barre d’onglets changent immédiatement.
Un détail à adopter : si la valeur du contexte est un nouvel objet à chaque rendu du fournisseur, tous les consommateurs se rérendent dès que le fournisseur le fait. Mémoriser cette valeur avec useMemo, en utilisant le thème comme clé, évite ce problème. Dans une application aussi petite, cela a peu d’importance, mais cela devient pertinent lorsque de nombreux composants consomment le contexte.
Conserver le choix avec AsyncStorage
Clicquer sur un thème fonctionne désormais, mais le choix est perdu lors du rechargement. Étendez la fonction setTheme de manière à ce qu’en plus de mettre à jour l’état, elle enregistre le choix dans AsyncStorage. Stocker la clé du thème plutôt que l’objet entier est le choix le plus fiable : si vous modifiez ultérieurement les couleurs d’un thème dans themes.json, les utilisateurs obtiendront la version mise à jour au lieu d’une copie obsolète.
Rétablir le thème au démarrage avec useEffect
La dernière étape consiste à lire le thème enregistré lorsque l’application démarre. useEffect s’exécute après que le composant ait été rendu, ce qui en fait l’endroit idéal pour gérer des effets secondaires tels que la lecture du stockage. En termes de classes, cela correspond à ce que componentDidMount et componentDidUpdate étaient utilisés pour gérer ; la comparaison parfois avancée avec componentWillReceiveProps est trompeuse, car les effets s’exécutent après le rendu et non avant l’arrivée de nouvelles propriétés.
Dans ThemeContextProvider, ajoutez un effet qui lit la clé enregistrée dans AsyncStorage, trouve le thème correspondant et appelle le setteur d’état. Deux détails permettent à cela de fonctionner correctement :
- Transmettez un tableau de dépendances vide. L’effet doit s’exécuter une seule fois, lors du montage du fournisseur, et non après chaque rendu. La référence API des Hooks explique comment le tableau de dépendances contrôle le moment où un effet est déclenché.
- Gérez l’état de chargement. AsyncStorage est asynchrone, donc le premier rendu a lieu avant que le thème enregistré ne soit connu. Suivez l’avancement du chargement et ne affichez rien (ou une vue temporaire) jusqu’à ce qu’il soit terminé. Sinon, les utilisateurs voient brièvement le thème par défaut avant que leur propre choix n’apparaisse.
Gérez également le cas où rien n’a encore été enregistré, ou lorsque la clé enregistrée ne correspond plus à un thème, en revenant au thème par défaut. Ainsi, la sélection est conservée après la redémarrage de l’application.
En résumé
Le fournisseur final est un composant fonctionnel unique qui gère l’état du thème, persiste les modifications, les restaure au démarrage et expose tout via le contexte. Par rapport à une version basée sur des classes, la logique est plus concise et se lit de haut en bas.
Quelques principes s’appliquent également aux autres fonctionnalités :
- Garder les préférences d’interface partagées dans un fournisseur de contexte proche de la racine, et exposer un setteur en même temps que la valeur.
- Persister des identifiants, et non des objets complets, afin que les modifications de données ne laissent pas de copies obsolètes sur les appareils.
- Considérer les lectures asynchrones au démarrage comme un état de chargement plutôt que d’afficher des valeurs par défaut pour les remplacer ultérieurement.
useReducer pour des états plus complexes, useRef pour des valeurs modifiables qui ne doivent pas déclencher de réaffichages, et useLayoutEffect pour des opérations qui doivent avoir lieu avant le rendu. La conversion d’un composant de classe existant est un bon moyen de s’entraîner.Lectures complémentaires
- Pourquoi Codegen impose la préparation des spécifications d’abord pour React Native TurboModules — Découvrez comment React Native Codegen transforme la spécification TypeScript de TurboModule en un contrat à l’heure de la compilation, ce qui se passe lorsque vous l’omettez, et où cette règle cesse d’être applicable.
- Planifier une mise à jour du Expo SDK 58 : iOS 27, React Native 0.88 et nouveaux outils — Une présentation pratique de la version bêta du Expo SDK 58 : quels changements pour iOS 27 et React Native 0.88, quelles fonctionnalités sont expérimentales, et comment tester la mise à jour en toute sécurité.
- Traiter les dossiers natifs comme résultats de construction avec Expo Prebuild et CNG — Comment la génération native continue permet à une application Expo d’utiliser des modules natifs personnalisés, des plugins de configuration et des secrets EAS sans avoir à enregistrer ou modifier manuellement les dossiers iOS et Android.
- Mises à jour OTA auto-hébergées pour React Native base avec hot-updater et Supabase — Mettez en place des mises à jour JavaScript sans connexion dans une application React Native base grâce à hot-updater et Supabase : initialisation, un hook de mise à jour silencieuse, des canaux, des scripts de déploiement et une fonction de réversion.