I'm new to React and I've created a simple hook to allow some components to subscribe to MQTT messages through a global connection to the broker stored in a context. I'm hoping to encapsulate the MQTT bits into this one file and allow my low-level components to read values from it and publish to it while letting the middle layers of the component tree ignore MQTT and do the layout. I'm open to suggestions on the design of this app if redux would be better or anything like that.
I'm experiencing a race condition where my values are only show on some page refreshes but not others. Confusingly, useEffect is not being called more than twice early on and I was expecting it to be called on every page render. Perhaps that's not occurring with each update to the mqtt incoming on('message')
. I'd like it to respond when a message comes in.
Furthermore, annoyingly my mqtt.connect is called about 4 times when I run this, I think because it's trying again so quickly before it actually connects. The if (client == null)
has not changed yet.
src/App.tsx
:
import Welcome from "./components/Welcome"
import ReadOnlyWidget from "./components/ReadOnlyWidget"
import { useMqtt, MqttProvider } from "./MqttContext"
const url = 'ws://10.0.200.10:9001'
export default function App() {
return (
<MqttProvider brokerUrl={url}>
<ReadOnlyWidget topic="/sample/tower-mill/drive/feed" field="feed_distance" />
<ReadOnlyWidget topic="/sample/tower-mill/drive/feed" field="feed_velocity" />
</MqttProvider>
);
}
src/MqttContext.tsx
:
import React from "react"
import mqtt from 'precompiled-mqtt'
import _ from 'lodash'
export const MqttContext = React.createContext(null)
export const MqttProvider = ({ brokerUrl, children }) => {
const [client, setClient] = React.useState(null)
const [messages, setMessages] = React.useState({})
if (client == null) {
const newClient = mqtt.connect(brokerUrl)
newClient.on('connect', () => {
console.log("new client connected")
})
newClient.on('disconnect', () => {
console.log('new client disconnected')
setClient(null)
})
newClient.on('message', (topic, message, packet) => {
const json = JSON.parse(new TextDecoder("utf-8").decode(message))
console.log(json)
setMessages(_.set(messages, topic, json))
})
setClient(newClient)
}
return (
<MqttContext.Provider value={{ client, messages }}>
{children}
</MqttContext.Provider>
)
}
export const useMqtt = ({topic, field}) => {
const mqttContext = React.useContext(MqttContext)
const [value, setValue] = React.useState(null)
mqttContext.client.subscribe(topic)
React.useEffect(() => {
console.log("use effect")
setValue(_.get(mqttContext.messages, [topic, field]))
})
return value
}
src/components/ReadOnlyWidget.tsx
:
import React from 'react';
import { useMqtt } from "../MqttContext"
export default (props) => {
const value = useMqtt({topic: props.topic, field: props.field})
return (
<p>{props.topic} {props.field} {value}</p>
)
}