O MQTT com React Native dá ao seu app mobile dados de dispositivos ao vivo sem ficar consultando uma API. Neste tutorial você vai conectar um app React Native (ou Expo) a um broker MQTT gerenciado com MQTT.js sobre WebSocket seguro, encapsulá-lo em um hook reutilizável, enviar comandos a um dispositivo e tratar as transições entre segundo e primeiro plano que quebram a maioria das primeiras tentativas.
Por que WebSocket para MQTT com React Native
O runtime JavaScript do React Native não tem sockets TCP brutos. Você pode adicioná-los com um módulo nativo, mas isso significa etapas de build nativo, dev clients do Expo e bugs específicos de plataforma. O React Native já traz um WebSocket compatível com os padrões, e o MQTT.js sabe rodar MQTT sobre ele. O resultado é um cliente JavaScript puro que funciona no Expo Go, no Android e no iOS sem código nativo.
O ConnectMQ expõe WebSocket seguro na porta 8084 com o caminho /mqtt, usando um certificado publicamente confiável, então a URL de que você precisa é:
wss://broker.connectmq.com:8084/mqtt
A porta 8084 também passa pela maioria das redes corporativas e de operadoras móveis, porque parece HTTPS. Para o contexto completo, veja MQTT sobre WebSocket.
Configuração do projeto
Crie um projeto e instale o MQTT.js mais um polyfill de URL (a implementação nativa de URL do React Native é incompleta, e o MQTT.js faz o parse da URL do broker):
npx create-expo-app@latest mqtt-demo
cd mqtt-demo
npx expo install react-native-url-polyfill
npm install mqtt
O MQTT.js 5 declara uma entrada react-native nos exports do pacote que aponta para o seu bundle de navegador autocontido, então as versões recentes do Metro resolvem import mqtt from "mqtt" corretamente. Se o seu bundler pegar o build de Node (você verá erros sobre net, tls ou fs), importe o bundle explicitamente com import mqtt from "mqtt/dist/mqtt.esm".
Logo no topo do seu arquivo de entrada (app/_layout.tsx no Expo Router, ou index.js em um app bare), carregue o polyfill uma vez:
import "react-native-url-polyfill/auto";
Construindo um hook MQTT para React Native
Um hook personalizado mantém a lógica de conexão fora dos componentes e facilita compartilhar um cliente entre telas. Esta versão conecta ao montar, assina, expõe as mensagens e o estado da conexão e reage às mudanças de 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 };
}
Repare em client.end(true): o true força o fechamento imediato do socket em vez de esperar pelas mensagens em trânsito, o que importa porque o sistema operacional dá apenas alguns segundos antes de suspender o app.
Tratando reconexões entre segundo e primeiro plano
É aqui que a maioria dos apps MQTT com React Native erra, então o assunto merece uma seção própria. Veja o que realmente acontece em um celular:
- O usuário troca de app. Em segundos o iOS suspende a sua thread JavaScript; o Android pode mantê-la viva por um breve momento e depois restringe o acesso à rede.
- O WebSocket continua "aberto" do ponto de vista do app, mas nenhum ping de keep-alive é enviado. O broker encerra a sessão depois de cerca de 1,5 vez o intervalo de keep-alive.
- O usuário volta. O MQTT.js acaba percebendo o socket morto, mas só depois do próprio timeout, então a UI mostra valores desatualizados e "connected" por vários segundos.
O hook evita isso fechando a conexão de propósito em background e abrindo uma conexão nova em active. Mais algumas regras ajudam:
- Assine dentro do handler de
connect, e não uma única vez depois de criar o cliente. O MQTT.js refaz as assinaturas nas próprias reconexões por padrão, mas um cliente totalmente novo após voltar ao primeiro plano precisa assinar de novo, e fazer isso no handler cobre os dois casos. - Use retained messages para estado. Se os dispositivos publicam o último status com a flag retain, um app recém-conectado recebe o valor atual imediatamente em vez de esperar a próxima atualização. Veja retained messages no MQTT.
- Não mantenha conexões em segundo plano para alertas. Use as push notifications da plataforma para tudo que precisa chegar a um app fechado; o MQTT é para dados ao vivo enquanto o app está aberto.
- Ignore
inactiveno iOS. Ele dispara com a central de notificações ou uma chamada recebida; derrubar a conexão ali causa reconexões desnecessárias.
| AppState | O que o hook faz | Por quê |
|---|---|---|
active |
connect() se não houver cliente |
Socket e assinaturas novos |
inactive (somente iOS) |
nada | Transitório, ex.: central de controle aberta |
background |
disconnect() |
O sistema vai suspender o socket de qualquer forma |
Usando o hook em uma tela
import { View, Text, Switch, StyleSheet } from "react-native";
import { useMemo, useState } from "react";
import { useMqtt } from "../hooks/useMqtt";
const PREFIX = "connectmq/demo/rn-7f3a91"; // escolha o seu próprio prefixo aleatório
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" },
});
O useMemo no array de topics é importante: sem ele, um novo array a cada render muda o callback connect e provoca loops de reconexão.
Compartilhando uma conexão entre telas
Do jeito que está escrito, o hook abre uma conexão por componente que o utiliza. Para um app com várias telas, leve o cliente para um provider de contexto React na raiz: o provider chama useMqtt uma vez com a união dos topics e expõe messages e publish pelo contexto, e cada tela lê apenas os topics de que precisa. Uma conexão por app mantém você bem dentro dos limites de conexões, já que o plano gratuito Serverless permite 25 conexões simultâneas no total, e evita que o broker veja várias sessões concorrentes vindas de um mesmo celular. Se as telas precisarem de assinaturas diferentes ao longo do tempo, adicione funções subscribe e unsubscribe ao contexto em vez de reconectar.
Testando no servidor MQTT público
O servidor MQTT público (gratuito) em broker.connectmq.com aceita clientes anônimos, então você pode testar sem conta. Mantenha o prefixo aleatório, porque o servidor público pode ser lido por qualquer pessoa e serve apenas para testes. Simule o tanque e a bomba, como os de uma estação elevatória de saneamento, a partir de um terminal:
PREFIX="connectmq/demo/rn-7f3a91"
# Retido para que o app o veja logo após conectar
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
Deixe o app em segundo plano por um minuto, traga-o de volta e confirme que o status vai de connecting para connected e que os valores retidos reaparecem na hora. O servidor MQTT público permite até 10 mensagens por segundo por cliente, mais do que suficiente para uma UI.
Produção: broker privado e credenciais
Para um app publicado, migre para um broker privado em um broker MQTT gerenciado:
- Crie um broker no portal do ConnectMQ e copie o usuário, a senha e o client ID gerados.
- Passe-os para o hook:
useMqtt({ topics, username: "YOUR_USERNAME", password: "YOUR_PASSWORD", clientId: "YOUR_CLIENT_ID" }). - Guarde os segredos com
expo-secure-storeou no keychain da plataforma, nunca no bundle JavaScript, que qualquer pessoa pode descompactar.
O ConnectMQ vincula os client IDs às credenciais que emite, então dois celulares não podem compartilhar uma mesma identidade ao mesmo tempo. Para um app multiusuário, faça o seu backend entregar a cada usuário as próprias credenciais após o login. O plano gratuito Serverless permite até 25 conexões simultâneas, o que cobre um piloto de equipe; os planos Serverless pagos chegam a 1.000.
Solução de problemas
| Sintoma | Causa | Correção |
|---|---|---|
Cannot read property 'protocol' of undefined ou erros de URL |
Falta o polyfill de URL | Importe react-native-url-polyfill/auto primeiro |
Erros mencionando net ou tls |
O Metro resolveu o build de Node | Importe mqtt/dist/mqtt.esm |
| Conecta no simulador, dá timeout no celular | Porta errada ou ws:// em vez de wss:// |
Use wss://...:8084/mqtt |
| Loop de reconexão a cada render | Array topics instável |
Envolva em useMemo |
| Dados desatualizados ao retomar | O socket morreu em segundo plano | Desconecte em background, reconecte em active |
Comece a usar o ConnectMQ
Aponte o seu app React Native para um broker privado em minutos. Cadastre-se em app.connectmq.com, crie um broker Serverless gratuito sem cartão de crédito e use o cliente no navegador e o explorador de topics ao vivo para ver os comandos do seu app chegando. O ConnectMQ cuida do TLS, da escalabilidade e da operação do broker, então o seu hook continua sendo o único código MQTT que você mantém. Para outras stacks, o guia de bibliotecas cliente MQTT e o tutorial de MQTT com Flutter mostram o mesmo padrão.
Perguntas frequentes
O React Native pode usar MQTT sobre TCP em vez de WebSocket?
Só com um módulo nativo de socket TCP, o que acrescenta complexidade de build nativo. WebSocket seguro na porta 8084 funciona de imediato com o MQTT.js e é a opção mais simples e portável.
Isso funciona com o Expo Go?
Sim. O MQTT.js sobre WebSocket é JavaScript puro, então roda no Expo Go sem um dev client personalizado.
Por que a minha conexão MQTT com React Native cai em segundo plano?
Os sistemas operacionais móveis suspendem apps em segundo plano e seus sockets de rede. Desconecte em background, reconecte em active e use push notifications para alertas que precisam chegar a um app fechado.
Devo usar MQTT 5 ou 3.1.1 no React Native?
Os dois funcionam com o ConnectMQ. O MQTT 5 traz reason codes mais claros quando algo falha, o que facilita depurar problemas de conexão no mobile.



