Accueil / Articles / Changement persistant de thème dans React Native à l’aide de Context et de Hooks

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.

1618 mots

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.
  • Lorsque vous serez à l’aise avec ces trois Hooks, explorez 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