React app using empty state inside onMessage from WEbsocket - javascript

I've created a application for my company to manage diffrent things.
I first started realizing it with Class-Components, now switched to Functional components.
The issue that i'm facing is, that in one function inside Websocket onMessage uses old/empty state.
This is how the application currently is built.
const DocumentControl = ({ createWebsocket, logOut }) => {
const [elementList, setElementList] = useState([]);
const webSocket = useRef(null);
useEffect(() => {
webSocket.current = createWebsocket();
webSocket.current.onmessage = (messageEvent) => {
console.log(elementList);
if (devComm) {
console.log(JSON.parse(messageEvent.data));
}
processMessage(JSON.parse(messageEvent.data));
};
window.addEventListener("beforeunload", () => {
logOut(true);
});
}, []);
useEffect(() => {
loadElements();
}, [menu]);
const processMessage = (message) => {
var newArr, index;
switch (message.type) {
case "document":
var elementItem = message.data;
newArr = [...elementList]; // here elementList is always []
...
break;
default:
if (devComm) {
console.log("---Unknown WSMessage!---");
}
}
};
}
There is obviously more logic but this may be sufficient to describe my problem.
Basically i'm loading from the Server my ElementList if the Menu changes.
Whenever an Element on the Server-Side is updated, I send a message through Websockets to all clients, so all are up-to-date.
---My Issue---
Loading the Elements from Server and using setElementList works fine. But whenever I receive a message through websockets, the state elementList is always empty.
I made a button which displays the current elementList and there the List is correctly.
For example: I have 4 Elements in my list. Because of Message in Websockets i want to add to my current list the new Element. But the elementList is [] inside processMessage, so I add the new element to an [] and after setElementList all other elements are gone (because of empty array previously)
My first thought was, that (because I get the Websocket element from parent) maybe it uses diffrent instance of elementList, which is initialized as empty [].
But it would not make sense, because setElementList still affects the "original" elementList.
I also tried using elementList with useRef(elementList) and accessing it with ref.current but still didn't change the outcome.
here is how the createWebsocket is implemented:
const createWebsocket = () => {
if (!webSocket.current) {
var uri = baseURI.replace("http://", "ws://");
console.log("Create new Websocket");
console.log("Websocket: " + uri + "websocket/");
let socket = new WebSocket(uri + "websocket/" + user.Token);
socket.onopen = () => {};
socket.onclose = (closeEvent) => {
console.log("closed socket:");
if (closeEvent.code !== 3001) {
logOut(false);
if (closeEvent !== null && closeEvent.reason.length > 0) {
alert(closeEvent.reason);
}
}
};
socket.onerror = () => {
logOut(true, false);
alert("Something went wrong");
};
webSocket.current = socket;
}
return webSocket.current;
};
Any Ideas why elementList is diffrent inside ProcessMessage then in other functions of the Component?
--------- UPDATE -------
I could temporary fix it, by using this workaround:
const elementsRef = useRef([]);
useEffect(() => {
elementsRef.current = elementList;
}, [elementList]);
and then accessing elementsRef.current
But there must be an more elegant soltuion to this

Related

How to handle toasts with timeout on React + NextJS

I got a strange problem. Let's start with my stack, we're using React + NextJS + Tailwind css . I made an toast component using react context, inside my context I have methods which is managing my toasts. Now let's take a look at my code(I cant show all my code , but here is part im getting troubles with)
Context file:
export const ToastContextProvider:FC<ToastProps> = ({ children }) => {
const [toasts, setToasts] = useState<ToastComponentProps[]>([]);
const generateId = () => Math.random().toString(36).substring(2, 9);
const removeToast = (_id: string): void => {
setToasts(toasts.filter(({ id }) => id !== _id));
};
const showToast = (toast: ToastComponentProps) => {
const toastWithId = { ...toast, id: generateId() };
setToasts((oldToasts) => [...oldToasts, toastWithId]);
if (toast.timeout !== 0) {
setTimeout(() => removeToast(toastWithId.id), toast.timeout ?? 3000);
}
};
As you can see , every toast object have a proprety name timeout , when timeout = 0 we dont have to remove it automatically by code , and user have to close the toast by himself.
The main problem:
I call a toasts with timeout and without , and they are deleted randomly. I have tried a lot of things but Im still stuck.

React: Load objects with a REST call and prepend new from WebSocket events

I want to implement a functionality that would load a list objects from a REST call when the page loads the first time and then it would consume new objects from WebSocket events and update/append them correspondingly.
const [objects, setObjects] = useState([])
useEffect(() => {
axios.get(`${process.env.REACT_APP_REST_API}/objects`)
.then(resp => setObjects(resp.data))
}, [])
const ws = new WebSocket(`${process.env.REACT_APP_WS_API}/objects`)
ws.onmessage = (event) => {
const obj = JSON.parse(event.data)
const items = [...objects]
const index = items.findIndex((it) => it.id === obj.id)
if (index === -1) {
items.push(obj)
} else {
items[index] = obj
}
setObjects(items)
};
This code doesn't work. It opens WS connections several times. Also the initial data disappears from time to time and only the last object is shown. I understand, that it is related to the fact that react re-renders the component. And the issue with disappearing elements is most likely related to the scope of the closure used in the WS handler. At the same time, I have no idea how to solve it in React.
Q: How can I preload elements with a REST call and keep updating them further consuming events from WebSocket in a React component?
Try put ws creation into useEffect.
UseState with arrays is different.
If you like, use return on useEffect for closing ws when component will be unmount.
const [objects, setObjects] = useState([])
useEffect(() => {
axios.get(`${process.env.REACT_APP_REST_API}/objects`)
.then(resp => setObjects(resp.data));
const ws = new WebSocket(`${process.env.REACT_APP_WS_API}/objects`)
ws.onmessage = (event) => {
const obj = JSON.parse(event.data)
const index = objects.findIndex((it) => it.id === obj.id)
if (index === -1) {
setObjects((current)=>[...current, obj]);
} else {
items[index] = obj
setObjects((current)=> current[index]= obj);
}
};
return () => { ws.close(); }
}, [])

What is the correct approach to modify react state from inside a socket listener?

I am making a real-time chat app with react native and socket.io and trying to update the messages state array with new data whenever the 'chat message' event fires. But inside the callback function, the value of messages is always an empty array, however, the desired behavior is that it contains the list of previous messages and just pushes the new data in the array.
I can't figure out what's going on here. I think maybe it's because of the callback function's closure. I wanted to know what is the correct way to modify state variables from inside the event listeners?
const [messages, setMessages] = useState([]);
const _onMessage = (data) => {
console.log(`${client.id} : ${JSON.stringify(data)}`);
console.log('new message');
};
const connect_socket = () => {
client.connect();
client.on('connect', _onConnect);
client.on('disconnect', _onDisconnect);
client.on('chat message', (data) => {
console.log(`${client.id} : ${JSON.stringify(data)}`);
console.log('new message');
console.log(messages); // this is always empty despite it being the state variable
const temp = messages.slice();
console.log(temp);
temp.push(data);
console.log(temp);
setMessages(temp);
});
};
const remove_socket = () => {
client.disconnect();
// client.removeListener('connect', _onConnect);
// client.removeListener('disconnect', _onDisconnect);
// client.removeListener('chat message', _onMessage);
client.off('connect');
client.off('disconnect');
client.off('chat message');
};

Print out Function (Debug Redux State and Actions without Redux Dev Tools)

I am doing some research into how Slack uses Redux. I am running custom Javascript on the page using the Chrome extension CJS.
I log the action and state changes. When the action is a function I can't log the function correctly.
Here is an excerpt from the console log:
...
[AP] store dispatch called: function d(e,n){return t(e,n,r)}
[AP] teamStore dispatch called: {"type":"[21] Navigate to a route","payload":{"routeName":"ROUTE_ENTITY","params":{"teamId":"TS6QSK7PA","entityId":"DU52E70NB"}},"error":false}
...
The code where I print the function is:
console.log("[AP] store dispatch called: " + (JSON.stringify(action) || action.toString()));
Here is full code code:
const teamStates = [];
const states = [];
let base;
let teamStore;
let store;
function subscribeToStores() {
const reactRoot = document.getElementsByClassName('p-client_container')[0];
try {
base = reactRoot._reactRootContainer._internalRoot.current
} catch (e) {
console.log('[AP] Could not find React Root');
}
if (!base) {
setTimeout(() => {
subscribeToStores();
}, 1);
} else {
console.log('[AP] Found React Root');
while (!store) {
try {
store = base.pendingProps.store;
} catch (e) {}
base = base.child
}
console.log('[AP] Found store');
console.log(JSON.stringify(store.getState()));
states.push(store.getState());
while (!teamStore) {
try {
teamStore = base.pendingProps.teamStore;
} catch (e) {}
base = base.child
}
console.log('[AP] Found teamStore');
console.log(JSON.stringify(teamStore.getState()));
teamStates.push(teamStore.getState());
var unsubscribe1 = teamStore.subscribe(() => {
teamStates.push(teamStore.getState());
console.log("[AP] teamStates length:" + teamStates.length);
console.log(JSON.stringify(teamStore.getState()));
})
var rawDispatchTeamStore = teamStore.dispatch;
teamStore.dispatch = (action) => {
console.log("[AP] teamStore dispatch called: " + (JSON.stringify(action) || action.toString()));
rawDispatchTeamStore(action);
}
var unsubscribe2 = store.subscribe(() => {
states.push(store.getState());
console.log("[AP] states length:" + states.length);
console.log(JSON.stringify(store.getState()));
})
var rawDispatchStore = store.dispatch;
store.dispatch = (action) => {
console.log("[AP] store dispatch called: " + (JSON.stringify(action) || action.toString()));
rawDispatchStore(action);
}
}
}
subscribeToStores();
I'm not sure how better you could log that function. Since the code is most likely minified, you won't be able to get its original name, except maybe through sourcemaps. What more information did you want?
That said, it's pretty strange to dispatch a function to begin with. I would be more curious how the reducer for store is handling functions since, normally, actions are supposed to be objects with a type property.

Firebase Functions: Cannot read property 'val' of undefined

I'm trying to add a Function in my Firebase Database, that creates/updates a combined property of two others, whenever they change.
The model is like this:
database/
sessions/
id/
day = "1"
room = "A100"
day_room = "1_A100"
And my function so far:
exports.combineOnDayChange = functions.database
.ref('/sessions/{sessionId}/day')
.onWrite(event => {
if (!event.data.exists()) {
return;
}
const day = event.data.val();
const room = event.data.ref.parent.child('room').data.val();
console.log(`Day: ${day}, Room: ${room}`)
return event.data.ref.parent.child("day_room").set(`${day}_${room}`)
});
exports.combineOnRoomChange = functions.database
.ref('/sessions/{sessionId}/room')
.onWrite(event => {
if (!event.data.exists()) {
return;
}
const room = event.data.val();
const day = event.data.ref.parent.child('day').data.val();
console.log(`Day: ${day}, Room: ${room}`)
return event.data.ref.parent.child("day_room").set(`${day}_${room}`)
});
But it's throwing this error:
TypeError: Cannot read property 'val' of undefined
I'm following the very first example in the Firebase Functions Get Started (Add the makeUppercase() function) and this is what it does in order to reach the entity reference:
event.data.ref.parent
Am I using the child() function wrongly? Any ideas?
When Cloud Functions triggers your code, it passes in a snapshot of the data that triggered the change.
In your code:
exports.combineOnDayChange = functions.database
.ref('/sessions/{sessionId}/day')
This means you event.data has the data for /sessions/{sessionId}/day. It does not contain any data from higher in the tree.
So when you call event.data.ref.parent, this points to a location in the database for which the data hasn't been loaded yet. If you want to load the additional data, you'll have to load it explicitly in your code:
exports.combineOnDayChange = functions.database
.ref('/sessions/{sessionId}/day')
.onWrite(event => {
if (!event.data.exists()) {
return;
}
const day = event.data.val();
const roomRef = event.data.ref.parent.child('room');
return roomRef.once("value").then(function(snapshot) {
const room = snapshot.val();
console.log(`Day: ${day}, Room: ${room}`)
return event.data.ref.parent.child("day_room").set(`${day}_${room}`)
});
});
Alternatively, consider triggering higher in your tree /sessions/{sessionId}. Doing so means that you get all the necessary data in event.data already, and also means you only need a single function:
exports.updateSyntheticSessionProperties = functions.database
.ref('/sessions/{sessionId}')
.onWrite(event => {
if (!event.data.exists()) {
return; // the session was deleted
}
const session = event.data.val();
const day_room = `${session.day}_${session.room}`;
if (day_room !== session.day_room) {
return event.data.ref.parent.child("day_room").set(`${day}_${room}`)
});
});

Categories

Resources