React Native Academy · Leçon

Écouteurs en temps réel et persistance hors connexion

Abonnez-vous à une collection Firestore avec onSnapshot, recevez les mises à jour en direct dans l'interface, puis activez la persistance hors connexion afin que l'application lise les données en cache sans connexion.

Leçon 4 sur 413 étapes

Écouteurs en temps réel et persistance hors connexion est une leçon React Native Academy gratuite sur CoddyKit. Ceci est la leçon 4 sur 4. Tu peux lire la leçon complète ci-dessous gratuitement — puis la pratiquer en direct dans le navigateur avec un éditeur de code intégré et un tuteur IA 24/7. Elle fait partie du parcours d'apprentissage React Native Academy, et ta progression se synchronise sur le web et l'application CoddyKit. Le cours React Native Academy comprend 4 leçons au total.

Écouteurs en temps réel et lectures ponctuelles

Firestore propose deux façons de lire les données : les lectures ponctuelles avec .get() et les écouteurs en temps réel avec .onSnapshot(). Un écouteur en temps réel maintient une connexion persistante ouverte et appelle votre fonction de rappel chaque fois que le document ou le résultat de la requête change, y compris lors du chargement initial des données.

Utilisez .get() lorsque vous n'avez besoin des données qu'une seule fois (par exemple pour charger les paramètres utilisateur au démarrage de l'application). Utilisez .onSnapshot() lorsque les données changent fréquemment et que l'interface doit rester à jour, comme pour un fil de discussion ou un score en direct.

Écouter un document

Appelez .onSnapshot(callback) sur une DocumentReference pour écouter les changements d'un document précis. La fonction de rappel est appelée immédiatement avec les données actuelles, puis à chaque modification du document.

.onSnapshot() renvoie une fonction de désabonnement. Stockez-la dans une variable et appelez-la lors du nettoyage de votre useEffect afin d'arrêter l'écoute lorsque le composant est démonté et d'éviter les fuites de mémoire.

import { useEffect, useState } from 'react';
import firestore from '@react-native-firebase/firestore';

export function usePost(postId: string) {
  const [post, setPost] = useState(null);

  useEffect(() => {
    const unsubscribe = firestore()
      .collection('posts')
      .doc(postId)
      .onSnapshot((snapshot) => {
        if (snapshot.exists) {
          setPost({ id: snapshot.id, ...snapshot.data() });
        }
      });

    return unsubscribe; // Stop listener on unmount
  }, [postId]);

  return post;
}

Écouter une requête de collection

Associez .onSnapshot() à une requête de collection pour recevoir des mises à jour en direct chaque fois qu'un document de l'ensemble de résultats est ajouté, modifié ou supprimé. Le QuerySnapshot transmis à la fonction de rappel contient tous les documents correspondants, et pas uniquement ceux qui ont changé.

Ainsi, chaque fois qu'un document change, vous reconstruisez la liste entière à partir de snapshot.docs. Cette méthode convient aux listes petites ou moyennes, et le cache local de Firestore la rend rapide, même sans nouvel aller-retour réseau pour les données mises en cache.

useEffect(() => {
  const unsubscribe = firestore()
    .collection('messages')
    .where('roomId', '==', roomId)
    .orderBy('createdAt', 'desc')
    .limit(50)
    .onSnapshot((snapshot) => {
      const messages = snapshot.docs.map((doc) => ({
        id: doc.id,
        ...doc.data(),
      }));
      setMessages(messages);
    });

  return unsubscribe;
}, [roomId]);

Utiliser DocumentChange pour gagner en efficacité

Au lieu de reconstruire la liste entière à chaque instantané, vous pouvez examiner le tableau des changements pour voir précisément ce qui a été ajouté, modifié ou supprimé. Chaque entrée de snapshot.docChanges() possède une propriété type : 'added', 'modified' ou 'removed'.

Cette approche est plus efficace pour les collections volumineuses où seuls quelques documents changent à la fois. Vous mettez à jour précisément le tableau d'état local au lieu de le remplacer entièrement.

snapshot.docChanges().forEach((change) => {
  const item = { id: change.doc.id, ...change.doc.data() };

  if (change.type === 'added') {
    setItems((prev) => [item, ...prev]);
  } else if (change.type === 'modified') {
    setItems((prev) => prev.map((i) => (i.id === item.id ? item : i)));
  } else if (change.type === 'removed') {
    setItems((prev) => prev.filter((i) => i.id !== item.id));
  }
});

Gérer les erreurs des écouteurs

.onSnapshot() accepte un deuxième argument pour la gestion des erreurs. Lorsque l'écouteur échoue, généralement à cause d'une erreur d'autorisation provenant des règles de sécurité Firestore, la fonction de rappel d'erreur est appelée avec une FirebaseError.

Les codes d'erreur courants comprennent permission-denied (les règles de sécurité ont bloqué la lecture) et unavailable (hors ligne et aucune donnée mise en cache). Fournissez toujours un gestionnaire d'erreurs afin de pouvoir afficher une interface de secours plutôt que d'échouer silencieusement.

const unsubscribe = firestore()
  .collection('private-data')
  .onSnapshot(
    (snapshot) => {
      setData(snapshot.docs.map((doc) => doc.data()));
    },
    (error) => {
      if (error.code === 'permission-denied') {
        setError('You do not have access to this data.');
      } else {
        setError('Could not load data. Check your connection.');
      }
    }
);

Persistance hors ligne Firestore

React Native Firebase active par défaut la persistance hors ligne sur les appareils mobiles. Cela signifie que Firestore met en cache tous les documents que vous lisez ou écoutez dans une base de données SQLite locale sur l'appareil. Lorsque l'appareil n'a pas accès à Internet, les lectures renvoient immédiatement les données mises en cache.

Aucune configuration n'est nécessaire pour l'activer : cela fonctionne automatiquement. Vous pouvez contrôler la limite de taille du cache (40 MB par défaut). Lorsque le cache est plein, Firestore supprime les documents les moins récemment utilisés. Vous pouvez également définir une taille illimitée avec cacheSizeBytes: firestore.CACHE_SIZE_UNLIMITED.

import firestore from '@react-native-firebase/firestore';

// Configure cache size (call before any Firestore operations)
await firestore().settings({
  cacheSizeBytes: firestore.CACHE_SIZE_UNLIMITED,
  persistence: true, // enabled by default on mobile
});

// After this, all reads and writes work offline automatically

Lire depuis le cache ou le serveur

Par défaut, .get() tente d'abord le serveur et utilise le cache en solution de repli si l'appareil est hors ligne. Vous pouvez modifier ce comportement en indiquant une option source : { source: 'cache' } lit toujours le cache local (instantanément, sans réseau) et { source: 'server' } utilise toujours le réseau.

Les lectures limitées au cache sont utiles pour les interfaces optimistes, lorsque vous souhaitez afficher immédiatement les données existantes pendant qu'une nouvelle requête réseau est en cours. Si le document ne se trouve pas dans le cache, une lecture limitée au cache renvoie une erreur.

// Always use cached data (instant, works offline)
const snapshot = await firestore()
  .collection('posts')
  .doc(postId)
  .get({ source: 'cache' });

// Always fetch from server (throws if offline)
const freshSnapshot = await firestore()
  .collection('posts')
  .doc(postId)
  .get({ source: 'server' });

// Default: server first, cache fallback
const snapshot2 = await firestore()
  .collection('posts')
  .doc(postId)
  .get();

Pagination avec des curseurs de requête

Pour les collections volumineuses, chargez les données par pages à l'aide de curseurs de requête : .startAfter(lastDocument). Conservez une référence vers le dernier document chargé et transmettez-la à la requête suivante pour obtenir la page suivante. C'est ce qu'on appelle la pagination fondée sur un curseur.

Stockez l'instantané du dernier document dans l'état. Lorsque l'utilisateur fait défiler la liste jusqu'en bas (onEndReached de FlatList), déclenchez une nouvelle requête commençant après le dernier document. Ajoutez les nouveaux résultats à la liste existante.

const [posts, setPosts] = useState([]);
const [lastDoc, setLastDoc] = useState(null);
const [hasMore, setHasMore] = useState(true);

async function loadMore() {
  if (!hasMore) return;

  let query = firestore()
    .collection('posts')
    .orderBy('createdAt', 'desc')
    .limit(20);

  if (lastDoc) {
    query = query.startAfter(lastDoc);
  }

  const snapshot = await query.get();
  if (snapshot.empty) {
    setHasMore(false);
    return;
  }

  setLastDoc(snapshot.docs[snapshot.docs.length - 1]);
  const newPosts = snapshot.docs.map((doc) => ({ id: doc.id, ...doc.data() }));
  setPosts((prev) => [...prev, ...newPosts]);
}

Index composites

Lorsque vous combinez .where() et .orderBy() sur des champs différents, Firestore exige un index composite. Sans celui-ci, la requête génère une erreur failed-precondition accompagnée d'une URL permettant de créer automatiquement l'index.

Les index composites sont configurés dans firestore.indexes.json et déployés avec Firebase CLI. Pendant le développement, cliquez simplement sur l'URL du journal d'erreur : Firebase crée l'index pour vous. En production, gérez toujours les index dans le code afin qu'ils soient suivis dans le contrôle de version.

// This query requires a composite index on (userId ASC, createdAt DESC)
const snapshot = await firestore()
  .collection('posts')
  .where('userId', '==', userId)
  .orderBy('createdAt', 'desc')
  .get();

// If the index is missing, you see:
// FirebaseError: The query requires an index.
// https://console.firebase.google.com/project/.../firestore/indexes?...
// Click the link to auto-create the index

Détacher les écouteurs lors d'un changement d'écran

Les écouteurs en temps réel consomment de la bande passante et de la batterie. Détachez-les lorsque l'utilisateur quitte un écran en utilisant le hook useFocusEffect de React Navigation à la place de useEffect. useFocusEffect exécute l'effet lorsque l'écran gagne le focus et effectue le nettoyage lorsque l'écran perd le focus.

Ainsi, vous ne maintenez un écouteur Firestore actif que lorsque l'écran concerné est visible. L'écouteur est suspendu, puis rétabli, lorsque l'utilisateur quitte l'écran et y revient.

import { useFocusEffect } from '@react-navigation/native';
import { useCallback } from 'react';

export function ChatScreen({ route }) {
  useFocusEffect(
    useCallback(() => {
      // Start listener when screen is focused
      const unsubscribe = firestore()
        .collection('messages')
        .onSnapshot(handleSnapshot);

      // Stop listener when screen loses focus
      return unsubscribe;
    }, [route.params.roomId])
  );
}

Mises à jour optimistes avec l'état local

Pour offrir une expérience utilisateur réactive, appliquez immédiatement les modifications à l'état local, avant que l'écriture dans Firestore ne soit terminée : c'est ce que l'on appelle une mise à jour optimiste. Affichez la modification immédiatement, puis laissez l'écouteur en temps réel la confirmer ou la corriger à l'arrivée de la réponse du serveur.

Par exemple, lorsque l'utilisateur aime une publication, incrémentez immédiatement le nombre de mentions J'aime dans l'état local. Si l'écriture sur le serveur échoue, rétablissez l'état local en diminuant le nombre et en affichant une notification d'erreur. Cela évite la latence perçue sur les connexions lentes.

async function toggleLike(postId: string, currentlyLiked: boolean) {
  // 1. Optimistically update local state
  setLiked(!currentlyLiked);
  setLikeCount((prev) => prev + (currentlyLiked ? -1 : 1));

  try {
    // 2. Persist to Firestore
    await firestore().collection('posts').doc(postId).update({
      likesCount: firestore.FieldValue.increment(currentlyLiked ? -1 : 1),
    });
  } catch (error) {
    // 3. Revert on failure
    setLiked(currentlyLiked);
    setLikeCount((prev) => prev + (currentlyLiked ? 1 : -1));
    Alert.alert('Error', 'Could not update like. Try again.');
  }
}

Vérification rapide

Vérifiez votre compréhension des concepts de développement mobile avec React Native abordés dans cette leçon.

Récapitulatif de la leçon

Dans cette leçon, vous avez appris : à attacher des écouteurs en temps réel avec .onSnapshot() pour mettre à jour les données en direct, comment la persistance hors ligne de Firestore met automatiquement les données en cache pour une utilisation hors ligne, et comment paginer de grandes collections avec une pagination basée sur le curseur startAfter. Ensuite, vous apprendrez à profiler les applications React Native et à éliminer les goulots d'étranglement des performances.

Gratuit pour commencer

Apprends JavaScript avec un tuteur IA — gratuit

Écris et exécute du vrai code dans ton navigateur, obtiens de l'aide instantanée d'un tuteur IA disponible 24h/24, et reprends là où tu t'es arrêté sur le web ou dans l'app.

Cours
30
Leçons
120

Questions Fréquemment Posées

La leçon « Écouteurs en temps réel et persistance hors connexion » est-elle gratuite ?

Oui — le texte complet de « Écouteurs en temps réel et persistance hors connexion » est gratuit à lire ici sur le web. Pour la pratiquer de manière interactive (un éditeur de code intégré et un tuteur IA 24/7) et déverrouiller le reste du cours React Native Academy, passe à CoddyKit PRO. Le cours React Native Academy comprend 4 leçons au total.

Qu'est-ce que j'apprendrai dans « Écouteurs en temps réel et persistance hors connexion » ?

Abonnez-vous à une collection Firestore avec onSnapshot, recevez les mises à jour en direct dans l'interface, puis activez la persistance hors connexion afin que l'application lise les données en cac… Tu pratiques React Native Academy avec du code pratique que tu exécutes directement dans le navigateur, et un tuteur IA 24/7 répond à tes questions au fur et à mesure que tu avances dans la leçon.

Dois-je avoir de l'expérience pour commencer React Native Academy ?

Aucune expérience préalable n'est requise. React Native Academy sur CoddyKit est structuré pour les débutants jusqu'aux apprenants avancés, donc tu peux commencer ici ou depuis le début et avancer à ton rythme. Ceci est la leçon 4 sur 4.

Combien de temps prend la leçon « Écouteurs en temps réel et persistance hors connexion » ?

La plupart des leçons CoddyKit prennent environ 5–10 minutes. Chacune est courte et interactive, tu progresses régulièrement et tu repiques exactement où tu t'es arrêté sur le web et l'app.

Peux-tu écrire et exécuter du code dans cette leçon React Native Academy ?

Oui. Chaque leçon React Native Academy inclut un éditeur de code intégré, tu écris et exécutes du vrai code directement dans ton navigateur et tu reçois des retours IA instantanés — aucune configuration locale requise.

Toutes les leçons de ce cours

  1. Connecter React Native Firebase à votre projet
  2. Authentification par e-mail et téléphone avec Firebase
  3. Lire et écrire des documents Firestore
  4. Écouteurs en temps réel et persistance hors connexion
← Retour à React Native Academy