Search code examples
next.jsreact-hooksmqttnext.js13mqtt.js

useState Set method is not updating the variable value


I am trying to use the library 'async-mqtt' in order to use MQTT protocol on a Next.js app. I retrieve MQTT messages as expected, when I log them I can see them in the local terminal (not in the browser).

basically I am trying to simply use the setData method of useState I created. I am trying to use it in the mqtt message event, but I can't see any change on the UI. I don't understand why setData is not working inside the MQTT event, but of course works from the test button created. of course there is something I am missing.

I also tried to use useEffect with data as a dependency in the dependency array, but is not getting called, except when I set data manually from the button, of course.

I tried looking to similar issues, but none of them seems to fit my case.

Here is my current code:

"use client"
import React, { useEffect, useState } from 'react';

const MQTTSubscriber = () =\> {
const\[data, setData\] = useState()

    const MQTT = require("async-mqtt");
    const client = MQTT.connect("tcp://broker.hivemq.com");
    
    const doStuff = async () => {
      console.log("Starting");
      try {
        //await client.publish("my topic", "It works!");
        await client.subscribe("my topic")
        console.log("Hello world")
        // This line doesn't run until the server responds to the publish
        //await client.end();
        // This line doesn't run until the client has disconnected without error
        console.log("Done");
      } catch (e){
        // Do something about it!
        console.log(e.stack);
        process.exit();
    }

}

client.on("message", (topic, message) => {
 const payload = { topic, message: message.toString() };
 console.log(message.toString());
 setData(message.toString()) //Here the issue, nothing happens.
});

client.on("connect", doStuff);

return (
 <div>
   <button onClick={() => setData("Test")}\> Set data manually test </button>
   <p>{"Data: " + data}</p>
 </div>
);
};

export default MQTTSubscriber;

I show you my package.json file:

{
  "name": "next-webgl-unity-mqtt",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "next lint"
  },
  "dependencies": {
    "async-mqtt": "^2.6.3",
    "autoprefixer": "10.4.16",
    "bufferutil": "^4.0.7",
    "eslint": "8.49.0",
    "eslint-config-next": "13.5.2",
    "mqtt": "^5.0.5",
    "next": "13.5.2",
    "postcss": "8.4.30",
    "react": "18.2.0",
    "react-dom": "18.2.0",
    "tailwindcss": "3.3.3",
    "utf-8-validate": "^5.0.10"
  }
}

I expect to have data received from mqtt displayed, using React useState.

do you know why this happens? or eventually do you know some workarounds for this?

if you need more info please don't hesitate to ask me!

thank you in advance

EDIT: the answer given by Hamude Shahin seems the right step to do, but doing it I now face another error in the client side, the error is the following WebSocket connection to 'ws://broker.hivemq.com/' failed


Solution

  • The issue you are facing might be related to how React re-renders components when state changes. In your code, you are using setData inside the MQTT message event handler to update the data state. However, React may not trigger a re-render of the component when state changes that occur outside React's own event handling, such as in your MQTT event handler.

    To ensure that changes to the data state trigger a re-render of your component, you can use the useEffect hook. You mentioned that you tried using useEffect, but it wasn't working. Let's try to address that issue.

    Here's how you can use useEffect to re-render your component when the data state changes:

    'use client'
    import React, { useEffect, useState } from 'react';
    
    const MQTTSubscriber = () => {
      const [data, setData] = useState('');
    
      const MQTT = require('async-mqtt');
      const client = MQTT.connect('tcp://broker.hivemq.com');
    
      const doStuff = async () => {
        console.log('Starting');
        try {
          await client.subscribe('my topic');
          console.log('Hello world');
          console.log('Done');
        } catch (e) {
          console.log(e.stack);
          process.exit();
        }
      };
    
      useEffect(() => {
        // This effect runs when 'data' state changes
        client.on('message', (topic, message) => {
          const payload = { topic, message: message.toString() };
          console.log(message.toString());
          setData(message.toString()); // This should trigger a re-render
        });
    
        client.on('connect', doStuff);
    
        return () => {
          // Clean up the MQTT client when the component unmounts
          client.end();
        };
      }, []); // The empty dependency array means this effect runs once after initial render
    
      return (
        <div>
          <button onClick={() => setData('Test')}>Set data manually test</button>
          <p>{'Data: ' + data}</p>
        </div>
      );
    };
    
    export default MQTTSubscriber;
    
    

    Update

    Based on your update. Check this issue.