skip to Main Content

I want to attach a payload of an mqtt message to an existing array in a state in REACT.

The problem is, that the state is not updated correctly. It is updated in the same component but when the next message arrives the state is again an empty array (as in the useState declaration).

The expected behavior should be, that the size is increasing by one by every incoming mqtt message.

const [stateMeasurements, setStateMeasurements] = useState([]);

useEffect(()=>{
  mqttClient.on('message', function (topic, message) {
    var payload = { topic, message: JSON.parse(message) }
    setStateMeasurements([...stateMeasurements, payload.message]);
    }
  )
}, []);

Because this effect is only rendered ones, I tried to move the setStateMeasurements call to an function outside the useEffect, too.

const [stateMeasurements, setStateMeasurements] = useState([]);

function funcSetStateMeasurements (message) {
    setStateMeasurements([...stateMeasurements, message]);
  }

useEffect(()=>{
  mqttClient.on('message', function (topic, message) {
    var payload = { topic, message: JSON.parse(message) }
    funcSetStateMeasurements(payload.message);
    }
  )
}, []);

Unfortunately it shows the same behavior…

I think it is again one of the basic problems of understanding the render behavior of REACT.

Thanks in advance!

2

Answers


  1. Since the stateMeasurements is not the dependency of useEffect hook, the value of stateMeasurements inside useEffect hook will always be the initial value []. To use the previous value of it, try Updating state based on the previous state

    You may pass an updater function to the setStateMeasurements setter function.

    const [stateMeasurements, setStateMeasurements] = useState([]);
    
    useEffect(()=>{
      mqttClient.on('message', function (topic, message) {
        var payload = { topic, message: JSON.parse(message) }
        setStateMeasurements(pre => [...pre, payload.message]);
      });
    }, []);
    

    An example: codesandbox

    Login or Signup to reply.
  2. The standard way of solving this issue would be to include stateMeasurements in the dependency array.

    To prevent a stacking amount of event handlers we also need to supply useEffect with a way to detach these events. To do this we return a function to useEffect that the hook uses for cleanup.

    const [stateMeasurements, setStateMeasurements] = useState([]);
    
    useEffect(() => {
      function handleMessage(topic, message) {
        var payload = { topic, message: JSON.parse(message) };
        setStateMeasurements([...stateMeasurements, payload.message]);
      }
    
      mqttClient.on('message', handleMessage);
      return () => mqttClient.off('message', handleMessage);
    }, [stateMeasurements]);
    

    This will attach an message handler that uses the value stateMeasurements. However each time stateMeasurements is updated, React will detach the old handler and assign a new handler with the updated stateMeasurements value.


    Since you are only trying to update stateMeasurements based on its previous value, using an updater function like described in the answer of Lin Du might be better suited. An updater function always receives the current value in its arguments. And since setStateMeasurements has a stable identity you don’t need to include it in the dependency array.

    Note that you probably still want to detach the event when the component unmounts, so adding return () => mqttClient.off(...) is still a good idea.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search