Tutoriais de IoT

MQTT com React Native: Apps IoT Mobile em Tempo Real via WebSocket

Crie um app MQTT com React Native usando MQTT.js sobre WebSocket seguro: um hook reutilizável, dados de dispositivos ao vivo, comandos e o tratamento correto de reconexões quando o app alterna entre segundo e primeiro plano.

App MQTT com React Native em um Android e em um iPhone recebendo dados IoT ao vivo de um broker na nuvem via WebSocket seguro
Rode isso no seu próprio broker MQTTTenha um broker MQTT privado e gerenciado no plano Free e comece com exemplos de código prontos.
Teste grátis Exemplos de código Sem cartão de crédito

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:

  1. 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.
  2. 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.
  3. 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 inactive no 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:

  1. Crie um broker no portal do ConnectMQ e copie o usuário, a senha e o client ID gerados.
  2. Passe-os para o hook: useMqtt({ topics, username: "YOUR_USERNAME", password: "YOUR_PASSWORD", clientId: "YOUR_CLIENT_ID" }).
  3. Guarde os segredos com expo-secure-store ou 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.

Artigos relacionados

Melhores Ferramentas CLI MQTT para Testes, Scripts e Depuração

Uma comparação prática entre mosquitto_pub/sub, MQTTX CLI, HiveMQ MQTT CLI, a CLI do MQTT.js, curl e mqttui, com o mesmo teste rodando no broker.connectmq.com, para você escolher a ferramenta certa para scripts e depuração.

9 min de leitura

Todos os artigos →

Seu broker MQTT privado está a poucos cliques de distância

Crie uma conta, receba as credenciais do seu broker e conecte seu primeiro dispositivo — sem servidores para instalar, atualizar ou escalar.