Tutoriels IoT

React Native MQTT : applications IoT mobiles temps réel via WebSocket

Créez une application React Native MQTT avec MQTT.js via WebSocket sécurisé : un hook réutilisable, des données d'appareils en direct, des commandes et une gestion correcte des reconnexions lorsque l'application passe de l'arrière-plan au premier plan.

Application React Native MQTT sur Android et iPhone recevant des données IoT en direct depuis un broker cloud via WebSocket sécurisé
Essayez-le sur votre propre broker MQTTObtenez un broker MQTT privé et managé avec l’offre Free et partez d’exemples de code prêts à l’emploi.
Essayer gratuitement Exemples de code Aucune carte bancaire requise

React Native MQTT apporte à votre application mobile des données d'appareils en direct sans interroger une API en boucle. Dans ce tutoriel, vous allez connecter une application React Native (ou Expo) à un broker cloud avec MQTT.js via WebSocket sécurisé, l'encapsuler dans un hook réutilisable, envoyer des commandes à un appareil et gérer les transitions entre arrière-plan et premier plan qui font échouer la plupart des premières tentatives.

Pourquoi WebSocket pour React Native MQTT

Le runtime JavaScript de React Native ne dispose pas de sockets TCP bruts. Vous pouvez les ajouter avec un module natif, mais cela implique des étapes de build natives, des dev clients Expo et des bugs propres à chaque plateforme. React Native fournit en revanche un WebSocket conforme aux standards, et MQTT.js sait déjà faire passer MQTT par ce canal. Le résultat est un client purement JavaScript qui fonctionne dans Expo Go, sur Android et sur iOS sans code natif.

ConnectMQ expose le WebSocket sécurisé sur le port 8084 avec le chemin /mqtt, avec un certificat reconnu publiquement ; l'URL dont vous avez besoin est donc :

wss://broker.connectmq.com:8084/mqtt

Le port 8084 traverse aussi la plupart des réseaux d'entreprise et d'opérateurs mobiles, car il ressemble à du HTTPS. Pour le contexte complet, consultez MQTT over WebSocket.

Configuration du projet

Créez un projet et installez MQTT.js ainsi qu'un polyfill d'URL (l'implémentation intégrée de URL dans React Native est incomplète, et MQTT.js analyse l'URL du broker) :

npx create-expo-app@latest mqtt-demo
cd mqtt-demo
npx expo install react-native-url-polyfill
npm install mqtt

MQTT.js 5 déclare une entrée react-native dans les exports de son package, qui pointe vers son bundle navigateur autonome ; les versions récentes de Metro résolvent donc correctement import mqtt from "mqtt". Si votre bundler récupère plutôt le build Node (vous verrez des erreurs concernant net, tls ou fs), importez explicitement le bundle avec import mqtt from "mqtt/dist/mqtt.esm".

Tout en haut de votre fichier d'entrée (app/_layout.tsx avec Expo Router, ou index.js dans une application bare), chargez le polyfill une seule fois :

import "react-native-url-polyfill/auto";

Construire un hook React Native MQTT

Un hook personnalisé garde la logique de connexion hors des composants et facilite le partage d'un même client entre plusieurs écrans. Cette version se connecte au montage, s'abonne, expose les messages et l'état de connexion, et réagit aux changements d'AppState.

hooks/useMqtt.js :

import { useEffect, useRef, useState, useCallback } from "react";
import { AppState } from "react-native";
import mqtt from "mqtt";

const BROKER_URL = "wss://broker.connectmq.com:8084/mqtt";

export function useMqtt({ topics, username, password, clientId }) {
  const clientRef = useRef(null);
  const [status, setStatus] = useState("idle");
  const [messages, setMessages] = useState({});
  const idRef = useRef(clientId || `rn-${Math.random().toString(16).slice(2, 10)}`);

  const connect = useCallback(() => {
    if (clientRef.current) return;

    const client = mqtt.connect(BROKER_URL, {
      clientId: idRef.current,
      username,
      password,
      protocolVersion: 5,
      clean: true,
      keepalive: 30,
      reconnectPeriod: 2000,
      connectTimeout: 10000,
    });
    clientRef.current = client;
    setStatus("connecting");

    client.on("connect", () => {
      setStatus("connected");
      client.subscribe(topics, { qos: 1 }, (err) => {
        if (err) console.warn("subscribe failed", err.message);
      });
    });
    client.on("reconnect", () => setStatus("reconnecting"));
    client.on("offline", () => setStatus("offline"));
    client.on("error", (err) => console.warn("mqtt error", err.message));
    client.on("message", (topic, payload) => {
      setMessages((prev) => ({ ...prev, [topic]: payload.toString() }));
    });
  }, [topics, username, password]);

  const disconnect = useCallback(() => {
    const client = clientRef.current;
    clientRef.current = null;
    if (client) client.end(true);
    setStatus("idle");
  }, []);

  const publish = useCallback((topic, message) => {
    clientRef.current?.publish(topic, message, { qos: 1 });
  }, []);

  useEffect(() => {
    connect();
    const sub = AppState.addEventListener("change", (state) => {
      if (state === "active") connect();
      else if (state === "background") disconnect();
    });
    return () => {
      sub.remove();
      disconnect();
    };
  }, [connect, disconnect]);

  return { status, messages, publish, clientId: idRef.current };
}

Notez client.end(true) : le true force la fermeture immédiate du socket au lieu d'attendre les messages en cours d'envoi, ce qui compte car le système d'exploitation ne vous laisse que quelques secondes avant de suspendre l'application.

Gérer les reconnexions entre arrière-plan et premier plan

C'est là que la plupart des applications React Native MQTT se trompent, d'où cette section dédiée. Voici ce qui se passe réellement sur un téléphone :

  1. L'utilisateur change d'application. En quelques secondes, iOS suspend votre thread JavaScript ; Android peut le maintenir brièvement en vie, puis restreint l'accès réseau.
  2. Le WebSocket reste « ouvert » du point de vue de l'application, mais aucun ping keep-alive n'est envoyé. Le broker ferme la session après environ 1,5 fois l'intervalle de keep-alive.
  3. L'utilisateur revient. MQTT.js finit par remarquer que le socket est mort, mais seulement après son propre délai d'expiration ; l'interface affiche donc des valeurs périmées et « connecté » pendant plusieurs secondes.

Le hook évite ce problème en fermant délibérément la connexion sur background et en ouvrant une connexion neuve sur active. Quelques règles supplémentaires aident :

  • Abonnez-vous dans le gestionnaire connect, et non une seule fois après la création du client. MQTT.js se réabonne par défaut lors de ses propres reconnexions, mais un client entièrement nouveau après le retour au premier plan a besoin d'un nouvel abonnement, et le faire dans le gestionnaire couvre les deux cas.
  • Utilisez les retained messages pour l'état. Si les appareils publient leur dernier état avec le flag retain, une application fraîchement connectée reçoit immédiatement la valeur actuelle au lieu d'attendre la prochaine mise à jour. Consultez les retained messages MQTT.
  • Ne maintenez pas de connexions en arrière-plan pour les alertes. Utilisez les notifications push de la plateforme pour tout ce qui doit atteindre une application fermée ; MQTT sert aux données en direct pendant que l'application est ouverte.
  • Ignorez inactive sur iOS. Cet état se déclenche pour le centre de notifications ou un appel entrant ; fermer la connexion à ce moment provoque des reconnexions inutiles.
AppState Ce que fait le hook Pourquoi
active connect() si aucun client n'existe Socket et abonnements neufs
inactive (iOS uniquement) rien Transitoire, par exemple centre de contrôle déroulé
background disconnect() Le système suspendra le socket de toute façon

Utiliser le hook dans un écran

import { View, Text, Switch, StyleSheet } from "react-native";
import { useMemo, useState } from "react";
import { useMqtt } from "../hooks/useMqtt";

const PREFIX = "connectmq/demo/rn-7f3a91";   // choisissez votre propre préfixe aléatoire

export default function DeviceScreen() {
  const topics = useMemo(() => [`${PREFIX}/pump/status`, `${PREFIX}/tank/level`], []);
  const { status, messages, publish } = useMqtt({ topics });
  const [pumpOn, setPumpOn] = useState(false);

  const level = messages[`${PREFIX}/tank/level`] ?? "--";
  const pumpStatus = messages[`${PREFIX}/pump/status`] ?? "unknown";

  return (
    <View style={styles.container}>
      <Text style={styles.status}>Broker: {status}</Text>
      <Text style={styles.value}>{level} %</Text>
      <Text>Tank level</Text>
      <Text style={styles.row}>Pump reports: {pumpStatus}</Text>
      <View style={styles.row}>
        <Text>Pump command</Text>
        <Switch
          value={pumpOn}
          disabled={status !== "connected"}
          onValueChange={(v) => {
            setPumpOn(v);
            publish(`${PREFIX}/pump/set`, v ? "ON" : "OFF");
          }}
        />
      </View>
    </View>
  );
}

const styles = StyleSheet.create({
  container: { flex: 1, padding: 24, justifyContent: "center", gap: 8 },
  status: { color: "#666" },
  value: { fontSize: 56, fontWeight: "600" },
  row: { marginTop: 16, flexDirection: "row", justifyContent: "space-between", alignItems: "center" },
});

Le useMemo sur le tableau de topics est important : sans lui, un nouveau tableau à chaque rendu modifie le callback connect et provoque des boucles de reconnexion.

Partager une seule connexion entre les écrans

Tel qu'il est écrit, le hook ouvre une connexion par composant qui l'utilise. Pour une application à plusieurs écrans, remontez le client dans un provider de contexte React à la racine : le provider appelle useMqtt une seule fois avec l'union des topics et expose messages et publish via le contexte, et chaque écran ne lit que les topics dont il a besoin. Une connexion par application vous maintient largement sous les limites de connexions, puisque le plan Serverless gratuit autorise 25 connexions simultanées au total, et évite que le broker voie plusieurs sessions concurrentes depuis un même téléphone. Si les écrans ont besoin d'abonnements différents au fil du temps, ajoutez des fonctions subscribe et unsubscribe au contexte au lieu de vous reconnecter.

Tester avec le serveur MQTT public gratuit

Le serveur MQTT public gratuit à l'adresse broker.connectmq.com accepte les clients anonymes : vous pouvez donc tester sans compte. Conservez le préfixe aléatoire, car la zone publique est lisible par tout le monde. Simulez le réservoir et la pompe depuis un terminal :

PREFIX="connectmq/demo/rn-7f3a91"

# Retenu pour que l'application le voie immédiatement après la connexion
mosquitto_pub -h broker.connectmq.com -p 1883 -i rn-sim-tank -r \
  -t "$PREFIX/tank/level" -m "64"

mosquitto_pub -h broker.connectmq.com -p 1883 -i rn-sim-pump -r \
  -t "$PREFIX/pump/status" -m "OFF"

mosquitto_sub -h broker.connectmq.com -p 1883 -i rn-sim-cmd -t "$PREFIX/pump/set" -v

Mettez l'application en arrière-plan pendant une minute, ramenez-la, et vérifiez que l'état passe de connecting à connected et que les valeurs retenues réapparaissent instantanément. Le serveur public autorise jusqu'à 10 messages par seconde et par client, ce qui est largement suffisant pour une interface.

Production : broker privé et identifiants

Pour une application publiée, passez à un broker privé sur un broker MQTT managé :

  1. Créez un broker dans le portail ConnectMQ et copiez le username, le mot de passe et le client ID générés.
  2. Passez-les au hook : useMqtt({ topics, username: "YOUR_USERNAME", password: "YOUR_PASSWORD", clientId: "YOUR_CLIENT_ID" }).
  3. Stockez les secrets avec expo-secure-store ou le trousseau de la plateforme, jamais dans le bundle JavaScript, que n'importe qui peut décompresser.

ConnectMQ lie les client IDs aux identifiants qu'il émet : deux téléphones ne peuvent donc pas partager la même identité en même temps. Pour une application multi-utilisateurs, faites en sorte que votre backend remette à chaque utilisateur ses propres identifiants après la connexion. Le plan Serverless gratuit autorise jusqu'à 25 connexions simultanées, ce qui couvre un pilote d'équipe ; les plans Serverless payants montent jusqu'à 1 000.

Dépannage

Symptôme Cause Solution
Cannot read property 'protocol' of undefined ou erreurs d'URL Polyfill d'URL manquant Importez react-native-url-polyfill/auto en premier
Erreurs mentionnant net ou tls Metro a résolu le build Node Importez mqtt/dist/mqtt.esm
Se connecte dans le simulateur, expire sur un téléphone Mauvais port ou ws:// au lieu de wss:// Utilisez wss://...:8084/mqtt
Boucle de reconnexion à chaque rendu Tableau topics instable Encapsulez-le dans useMemo
Données périmées après la reprise Le socket est mort en arrière-plan Déconnectez sur background, reconnectez sur active

Démarrer avec ConnectMQ

Pointez votre application React Native vers un broker privé en quelques minutes. Inscrivez-vous sur app.connectmq.com, créez un broker Serverless gratuit sans carte bancaire, et utilisez le client intégré au navigateur et l'explorateur de topics en direct pour voir arriver les commandes de votre application. ConnectMQ gère le TLS, la montée en charge et l'exploitation du broker, pour que votre hook reste le seul code MQTT à maintenir. Pour d'autres stacks, le guide des bibliothèques clientes MQTT et le tutoriel MQTT Flutter présentent le même modèle.

Questions fréquentes

React Native peut-il utiliser MQTT sur TCP au lieu de WebSocket ?

Uniquement avec un module natif de socket TCP, ce qui ajoute de la complexité au build natif. Le WebSocket sécurisé sur le port 8084 fonctionne immédiatement avec MQTT.js et constitue le choix le plus simple et le plus portable.

Cela fonctionne-t-il avec Expo Go ?

Oui. MQTT.js via WebSocket est du JavaScript pur : il fonctionne dans Expo Go sans dev client personnalisé.

Pourquoi ma connexion React Native MQTT se coupe-t-elle en arrière-plan ?

Les systèmes d'exploitation mobiles suspendent les applications en arrière-plan et leurs sockets réseau. Déconnectez-vous sur background, reconnectez-vous sur active, et utilisez les notifications push pour les alertes qui doivent atteindre une application fermée.

Dois-je utiliser MQTT 5 ou 3.1.1 avec React Native ?

Les deux fonctionnent avec ConnectMQ. MQTT 5 fournit des codes de raison plus clairs en cas d'échec, ce qui facilite le débogage des problèmes de connexion sur mobile.

Articles liés

Les meilleurs outils CLI MQTT pour tester, scripter et déboguer

Un comparatif pratique de mosquitto_pub/sub, MQTTX CLI, HiveMQ MQTT CLI, la CLI MQTT.js, curl et mqttui, avec le même test exécuté sur le serveur MQTT public gratuit broker.connectmq.com pour choisir le bon outil pour le scripting et le débogage.

9 min de lecture

Tous les articles →

Votre broker MQTT privé, à quelques clics

Créez un compte, récupérez les identifiants de votre broker et connectez votre premier appareil — aucun serveur à installer, mettre à jour ou dimensionner.