I'm sure this is a simple React rule, but I'm really struggling to find an answer to this problem because many people make similar mistakes that don't seem to apply to me (async misunderstanding, direct variable mutation, wrong dependency array, etc.) Let me explain.
I want to setup a heartbeat type system where players send messages back and forth every 3 seconds to all competitors to say "I'm alive and still connected." using WebRTC. The connection is fine, and sending/receiving messages works fine. Any player can send data about themselves, a heartbeat, or a few other things, but only one type of data at a time.
I have a function/useCallback (I tried both) called ProcessMessage
that reads these incoming messages. If the message contains data on a competitor, it gets set with setCompetitors
. If the data contains a heartbeat, it sends back an acknowledgement (or, it should). Heartbeats do arrive correctly.
In ProcessMessage
, the state competitors
is always the initial value of just []. However, in other places, competitors
updates just fine. When I use a breakpoint at the place heartbeats are being read, the competitors
state is correct. The only thing I can think of is that the ProcessMessage
function reads/sets incoming competitor data and also reads incoming heartbeats. It never does both at the same time, though. Competitor data come in, then a few seconds later, another message with a heart beat comes in, so I don't know why this would cause competitors
to always be the initial value. I have competitors
in my dependency array at the end of ProcessMessage
when I write it as a callback hook.
Just to be clear, this is NOT a case of:
/* Not this kind of problem, I'm pretty sure. I know setCompetitors is async */
setCompetitors(competitorData);
console.log(competitors);
I've cut out as much code as I can while still hopefully leaving enough to make sense of what's happening.
import React, { useCallback, useEffect, useState } from 'react';
import Peer from 'peerjs';
function App() {
const [myData, setMyData] = useState({/* default data, updates fine */});
const [competitors, setCompetitors] = useState([]);
/* Create WebRTC connection useEffect here, works fine */
// Heartbeats to all competitors - sends correctly
useEffect(() => {
const heartbeats = [];
competitors.forEach(competitor => { // competitors is fine here
heartbeats.push(setInterval(() => {
competitor.conn.send({heartbeat : {
stage: 1,
playerKey: myData.playerKey
}});
console.log("Heartbeat stage 1 sent"); // gets sent correctly
}, 3000));
});
return () => {
heartbeats.forEach(heartbeat => { clearInterval(heartbeat); });
}
}, [competitors]);
const ProcessMessage = useCallback((data) => {
/* If the data has competitor data, it won't have a heartbeat too */
/* check if data contains competitor data */
const newCompetitor = data.competitor;
if(newCompetitor) {
console.log("New competitor data received");
setCompetitors(oldCompetitors => {
let newCompetitors = [...oldCompetitors];
/* Some newCompetitor integration logic, partial updates, error checking, etc */
return newCompetitors;
});
}
/* Code to deal with other types of data here - works fine */
/* check if data contains a heartbeat */
const heartbeat = data.heartbeat;
if(heartbeat) {
/* This is the problem. competitors is always just [] */
const competitor = competitors.find(comp => comp.playerKey === heartbeat.playerKey);
if(competitor) {
/* Code to deal with incoming heartbeat - never fires*/
} else {
console.log("Heartbeat could not be returned"); // This always fires
}
}
}, [boardData, competitors]);
/* Lots more logic, functions, etc */
/* Beautiful return statement */
If any additional code could help clarify what's going on, please ask and I'll update the question. Thanks all.
btw, full code can be seen here: GitHub The offending line is 149