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 :
- 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.
- 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.
- 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
inactivesur 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é :
- Créez un broker dans le portail ConnectMQ et copiez le username, le mot de passe et le client ID générés.
- Passez-les au hook :
useMqtt({ topics, username: "YOUR_USERNAME", password: "YOUR_PASSWORD", clientId: "YOUR_CLIENT_ID" }). - Stockez les secrets avec
expo-secure-storeou 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.



