Mit React Native MQTT erhält Ihre mobile App Live-Gerätedaten, ohne eine API pollen zu müssen. In diesem Tutorial verbinden Sie eine React-Native- (oder Expo-)App mit MQTT.js über sicheren WebSocket mit einem Cloud-MQTT-Broker, kapseln die Logik in einen wiederverwendbaren Hook, senden Befehle an ein Gerät und behandeln die Übergänge zwischen Hintergrund und Vordergrund, an denen die meisten ersten Versuche scheitern. Das Muster eignet sich für Smart-Home-Apps ebenso wie für eine Instandhaltungs-App, mit der Techniker in der Halle Maschinenzustände und Füllstände live auf dem Smartphone verfolgen.
Warum WebSocket für React Native MQTT
Die JavaScript-Laufzeit von React Native bietet keine rohen TCP-Sockets. Sie können sie über ein natives Modul nachrüsten, doch das bedeutet native Build-Schritte, Expo Dev Clients und plattformspezifische Bugs. React Native liefert jedoch einen standardkonformen WebSocket mit, und MQTT.js kann MQTT bereits darüber betreiben. Das Ergebnis ist ein reiner JavaScript-Client, der ohne nativen Code in Expo Go, unter Android und unter iOS funktioniert.
ConnectMQ stellt sicheren WebSocket auf Port 8084 mit dem Pfad /mqtt und einem öffentlich vertrauenswürdigen Zertifikat bereit. Die benötigte URL lautet also:
wss://broker.connectmq.com:8084/mqtt
Port 8084 passiert außerdem die meisten Firmennetzwerke und Mobilfunknetze, weil er wie HTTPS aussieht. Den vollständigen Hintergrund finden Sie unter MQTT über WebSocket.
Projekt einrichten
Erstellen Sie ein Projekt und installieren Sie MQTT.js sowie ein URL-Polyfill (die eingebaute URL-Implementierung von React Native ist unvollständig, und MQTT.js parst die Broker-URL):
npx create-expo-app@latest mqtt-demo
cd mqtt-demo
npx expo install react-native-url-polyfill
npm install mqtt
MQTT.js 5 deklariert in seinen Package-Exports einen react-native-Eintrag, der auf sein eigenständiges Browser-Bundle zeigt, sodass aktuelle Metro-Versionen import mqtt from "mqtt" korrekt auflösen. Greift Ihr Bundler stattdessen auf den Node-Build zurück (erkennbar an Fehlern zu net, tls oder fs), importieren Sie das Bundle explizit mit import mqtt from "mqtt/dist/mqtt.esm".
Laden Sie das Polyfill einmal ganz oben in Ihrer Einstiegsdatei (app/_layout.tsx bei Expo Router oder index.js in einer Bare-App):
import "react-native-url-polyfill/auto";
Einen React Native MQTT Hook bauen
Ein eigener Hook hält die Verbindungslogik aus den Komponenten heraus und erleichtert es, einen Client über mehrere Screens hinweg zu teilen. Diese Version verbindet sich beim Mounten, abonniert Topics, stellt Nachrichten und Verbindungsstatus bereit und reagiert auf Änderungen von 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 };
}
Beachten Sie client.end(true): Das true schließt den Socket sofort, statt auf noch laufende Nachrichten zu warten. Das ist wichtig, weil das Betriebssystem Ihnen nur wenige Sekunden lässt, bevor es die App suspendiert.
Reconnects zwischen Hintergrund und Vordergrund
Hier machen die meisten React Native MQTT Apps Fehler, deshalb bekommt das Thema einen eigenen Abschnitt. Das passiert tatsächlich auf einem Telefon:
- Der Nutzer wechselt die App. Innerhalb von Sekunden suspendiert iOS Ihren JavaScript-Thread; Android hält ihn eventuell kurz am Leben und schränkt dann den Netzwerkzugriff ein.
- Aus Sicht der App bleibt der WebSocket "offen", aber es werden keine Keep-Alive-Pings mehr gesendet. Der Broker schließt die Session nach etwa dem 1,5-Fachen des Keep-Alive-Intervalls.
- Der Nutzer kehrt zurück. MQTT.js bemerkt den toten Socket irgendwann, aber erst nach seinem eigenen Timeout, sodass die UI mehrere Sekunden lang veraltete Werte und "connected" anzeigt.
Der Hook vermeidet das, indem er bei background bewusst trennt und bei active eine frische Verbindung öffnet. Einige weitere Regeln helfen:
- Abonnieren Sie im
connect-Handler, nicht einmalig nach dem Erstellen des Clients. MQTT.js abonniert bei eigenen Reconnects standardmäßig neu, ein brandneuer Client nach der Rückkehr in den Vordergrund braucht aber ein frisches Subscribe, und der Handler deckt beide Fälle ab. - Nutzen Sie Retained Messages für Zustände. Publizieren Geräte ihren letzten Status mit Retain-Flag, erhält eine frisch verbundene App den aktuellen Wert sofort, statt auf das nächste Update zu warten. Siehe MQTT Retained Messages.
- Halten Sie für Alarme keine Verbindungen im Hintergrund offen. Nutzen Sie Push-Benachrichtigungen der Plattform für alles, was eine geschlossene App erreichen muss; MQTT ist für Live-Daten bei geöffneter App gedacht.
- Ignorieren Sie
inactiveunter iOS. Dieser Zustand tritt beim Öffnen der Mitteilungszentrale oder bei einem eingehenden Anruf auf; wer dort die Verbindung abbaut, verursacht unnötige Reconnects.
| AppState | Was der Hook tut | Warum |
|---|---|---|
active |
connect(), falls kein Client existiert |
Frischer Socket und frische Abonnements |
inactive (nur iOS) |
nichts | Vorübergehend, z. B. heruntergezogenes Kontrollzentrum |
background |
disconnect() |
Das Betriebssystem suspendiert den Socket ohnehin |
Den Hook in einem Screen verwenden
import { View, Text, Switch, StyleSheet } from "react-native";
import { useMemo, useState } from "react";
import { useMqtt } from "../hooks/useMqtt";
const PREFIX = "connectmq/demo/rn-7f3a91"; // eigenes zufälliges Präfix wählen
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" },
});
useMemo für das Topic-Array ist wichtig: Ohne es verändert ein neues Array bei jedem Render den connect-Callback und verursacht Reconnect-Schleifen.
Eine Verbindung über mehrere Screens teilen
Der Hook in dieser Form öffnet eine Verbindung pro Komponente, die ihn nutzt. Bei einer App mit mehreren Screens heben Sie den Client in einen React-Context-Provider an der Wurzel: Der Provider ruft useMqtt einmal mit der Vereinigungsmenge aller Topics auf und stellt messages und publish über den Context bereit, und jeder Screen liest nur die Topics, die er braucht. Eine Verbindung pro App hält Sie deutlich unter den Verbindungslimits, denn der kostenlose Serverless-Plan erlaubt insgesamt 25 gleichzeitige Verbindungen, und sie verhindert, dass der Broker mehrere konkurrierende Sessions von einem Telefon sieht. Brauchen Screens im Laufe der Zeit unterschiedliche Abonnements, ergänzen Sie den Context um Funktionen für subscribe und unsubscribe, statt neu zu verbinden.
Gegen den öffentlichen MQTT-Broker testen
Der kostenlose öffentliche MQTT-Broker unter broker.connectmq.com akzeptiert anonyme Clients, sodass Sie ohne Konto testen können. Er ist für Tests und Prototypen gedacht, nicht für Produktionsdaten. Behalten Sie das zufällige Präfix bei, denn der öffentliche MQTT-Broker ist für alle lesbar. Simulieren Sie Tank und Pumpe aus einem Terminal:
PREFIX="connectmq/demo/rn-7f3a91"
# Retained, damit die App den Wert direkt nach dem Verbinden sieht
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
Schicken Sie die App für eine Minute in den Hintergrund, holen Sie sie zurück und prüfen Sie, dass der Status von connecting zu connected wechselt und die retained Werte sofort wieder erscheinen. Der öffentliche MQTT-Broker erlaubt bis zu 10 Nachrichten pro Sekunde und Client, was für eine UI mehr als genug ist.
Produktion: privater Broker und Zugangsdaten
Für eine ausgelieferte App wechseln Sie zu einem privaten Broker auf einem verwalteten MQTT-Broker:
- Erstellen Sie im ConnectMQ-Portal einen Broker und kopieren Sie den generierten Benutzernamen, das Passwort und die Client ID.
- Übergeben Sie sie an den Hook:
useMqtt({ topics, username: "YOUR_USERNAME", password: "YOUR_PASSWORD", clientId: "YOUR_CLIENT_ID" }). - Speichern Sie Secrets mit
expo-secure-storeoder in der Keychain der Plattform, niemals im JavaScript-Bundle, das jeder entpacken kann.
ConnectMQ bindet Client IDs an die ausgegebenen Zugangsdaten, sodass zwei Telefone nicht gleichzeitig dieselbe Identität nutzen können. Eindeutige Identitäten pro Gerät erleichtern außerdem die Nachvollziehbarkeit, die Sicherheitskonzepte nach IEC 62443 in Werksumgebungen verlangen. Bei einer Mehrbenutzer-App sollte Ihr Backend jedem Nutzer nach dem Login eigene Zugangsdaten übergeben. Der kostenlose Serverless-Plan erlaubt bis zu 25 gleichzeitige Verbindungen, was für einen Team-Pilot reicht; kostenpflichtige Serverless-Pläne gehen bis 1.000.
Fehlerbehebung
| Symptom | Ursache | Lösung |
|---|---|---|
Cannot read property 'protocol' of undefined oder URL-Fehler |
URL-Polyfill fehlt | react-native-url-polyfill/auto zuerst importieren |
Fehler mit Bezug auf net oder tls |
Metro hat den Node-Build aufgelöst | mqtt/dist/mqtt.esm importieren |
| Verbindet im Simulator, Timeout auf dem Telefon | Falscher Port oder ws:// statt wss:// |
wss://...:8084/mqtt verwenden |
| Reconnect-Schleife bei jedem Render | Instabiles topics-Array |
In useMemo kapseln |
| Veraltete Daten nach dem Fortsetzen | Socket ist im Hintergrund gestorben | Bei background trennen, bei active neu verbinden |
Starten Sie mit ConnectMQ
Verbinden Sie Ihre React-Native-App in wenigen Minuten mit einem privaten Broker. Registrieren Sie sich unter app.connectmq.com, erstellen Sie ohne Kreditkarte einen kostenlosen Serverless Broker und beobachten Sie mit dem Browser-Client und dem Live-Topic-Explorer, wie die Befehle Ihrer App ankommen. ConnectMQ kümmert sich um TLS, Skalierung und Broker-Betrieb, sodass Ihr Hook der einzige MQTT-Code bleibt, den Sie pflegen. Für andere Stacks zeigen der Leitfaden zu MQTT-Client-Bibliotheken und das Flutter MQTT Tutorial dasselbe Muster.
Häufig gestellte Fragen
Kann React Native MQTT über TCP statt WebSocket nutzen?
Nur mit einem nativen TCP-Socket-Modul, das zusätzliche native Build-Komplexität mit sich bringt. Sicherer WebSocket auf Port 8084 funktioniert mit MQTT.js sofort und ist die einfachere, portablere Wahl.
Funktioniert das mit Expo Go?
Ja. MQTT.js über WebSocket ist reines JavaScript und läuft daher ohne eigenen Dev Client in Expo Go.
Warum bricht meine React Native MQTT Verbindung im Hintergrund ab?
Mobile Betriebssysteme suspendieren Apps im Hintergrund samt ihren Netzwerk-Sockets. Trennen Sie bei background, verbinden Sie bei active neu und nutzen Sie Push-Benachrichtigungen für Alarme, die eine geschlossene App erreichen müssen.
Sollte ich in React Native MQTT 5 oder 3.1.1 verwenden?
Beides funktioniert mit ConnectMQ. MQTT 5 liefert bei Fehlern klarere Reason Codes, was die Fehlersuche bei Verbindungsproblemen auf Mobilgeräten erleichtert.



