How to stop memory leak in useEffect react - node.js

Hello I am facing a memory leak in my useEffect and I am getting this error "Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function."
I am trying to update my messages state when I emit the message from the backend.
my code looks like this
useEffect(() => {
socket.on("message", data => {
setMessages(prevVal => [...prevVal, {
author: data.id,
text: data.text
}]);
});
}, [socket]);
Please help and Thank You!

This happens if your component has already unmounted by the time the set state call is made, and this can happen because you're calling an asynchronous function socket.on which uses a callback.
Whenever you have these asynchronous side effects that may try to alter the state of your component, you should use a cleanup pattern.
So for example (given a socket.io socket)
useEffect(() => {
const onMessage = data => {
setMessages(prevVal => [...prevVal, {
author: data.id,
text: data.text
}]);
};
// Register listener
socket.on("message", onMessage);
// On cleanup, remove the attached listener
return () => socket.off("message", onMessage)
}, [socket]);
The cleanup function returned by useEffect will run when the component is unmounted, causing the set state call to be skipped.

Related

React Hooks: state not updating when called inside Socket.io handler

const [questionIndex, setQuestionIndex] = useState(0);
...
socket.on('next', () => {
console.log('hey')
setQuestionIndex(questionIndex + 1);
});
...
useEffect(() => {
console.log(questionIndex);
}, [questionIndex]);
I have a page that connects to a websocket using Socket.io. I am attempting to update the value of my questionIndex state variable when I receive a 'next' message from the socket. I seem to be receiving the message because 'hey' is printed, but questionIndex only updates after the first 'hey'. Afterwards, hey is printed, but questionIndex is not (I use the useEffect to print questionIndex when it is updated). Do you guys see anything wrong?
I was also facing this issue. Actually, we should not rely on values from state while updation.
right way to do this is
setQuestionIndex(QuestionIndex=>QuestioIndex+1)
For those wondering, it looks like the socket.on('next') function handler was using the original value of questionIndex (which was 0) every time. It seems like the function handler compiled this variable when it was bound rather than reading it at runtime. Not sure where in the documentation it specifies this behavior.
My solution was to change the function handler as such:
socket.on('next', (newIndex) => {
console.log('hey')
setQuestionIndex(newIndex);
});
This solves the problem by providing the value to set questionIndex to (rather than reading it upon receiving a 'next' event).
It looks like you open new socket every render and never disconnect it.
Try to place socket.on inside useEffect and the return value of that useEffect will be function that disconnect the socket.
useEffect(() => {
socket.on('next', () => {
console.log('hey')
setQuestionIndex(questionIndex + 1);
});
return () => socket.disconnect();
}, [questionIndex]);```

Vue Electron stuck when calling some Node libraries asynchronously

Context
I'm trying to build an app using the following stack:
Vue.js
Electron
I've developed simple Vue apps for a while now and I understand the basics.
What I'm trying to do here is to combine Vue and Electron using this awesome tool: Vue CLI Plugin Electron Builder.
While I've successfully managed to code simple apps with this stack, I'm facing issues when it comes to exploit Electron's nodeIntegration which is supposed to give Node access directly to my Vue code.
Issue
My asynchronous calls to some methods of the systeminformation library seem to be stuck (sometimes).
That is, when I want to assign a value my view's data by calling an asynchronous method of the systeminformation library, Vue seems to hang and await indefinitely.
The weired thing is that, sometimes, when I force refresh the page, I can briefly see in my console the log supposed to be output when the method returns data.
It's almost like refreshing the page forces Vue or something else to update.
I'd suspect the following leads but I might be wrong:
Vue's reactivity issue in my way of declaring and/or assigning.
Node integration issue making Vue+Node not "bounded" properly.
Electron's packaging of a Node library missing configuration to be used in Vue "as if it was a regular JS library".
The latter point is unclear to me as I've always used Electron+Vue to access Node libraries the same way I'd do for web libraries.
There might more to it that could lead to these issues but this is as far as my knowledge goes...
Steps to reproduce
You can create a Vue+Electron as follows:
vue create myapp
vue add electron-builder
yarn add systeminformation
I'm leaving default values for Vue CLI's interactive project creation as they don't have an impact on my issue.
As per Vue CLI Plugin Electron Builder's documentation on nodeIntegration, my vue.config.js file looks like this:
module.exports = {
pluginOptions: {
electronBuilder: {
nodeIntegration: true,
},
},
};
You can then use the snippets in Scenario 1 and Scenario 2.
Scenario 1
Let's consider the following example where I assign a value using Axios:
<template>
<pre>{{ test }}</pre>
</template>
<script>
import axios from "axios";
export default {
data() {
return {
test: null,
};
},
async mounted() {
console.log("Getting data from Axios...");
// Assign a value to the "test" data with an asynchronous HTTP call with Axios.
this.test = (
await axios.get("https://cat-fact.herokuapp.com/facts")
).data;
// It gets there as soon as the server responds.
console.log("Got it!", this.test);
},
};
</script>
Here, the asynchronous call works as expected, my test data updates properly and shows in my view as soon as the Axios call got an answer.
Scenario 2
Now, if I use the same logic to get my data from a method of the systeminformation library:
<template>
<pre>{{ test }}</pre>
</template>
<script>
import systeminformation from "systeminformation";
export default {
data() {
return {
test: null,
};
},
async mounted() {
console.log("Getting data from systeminformation...");
// Assign a value to the "test" data with an asynchronous call to a method of systeminformation.
this.test = await systeminformation.getStaticData();
// It just won't get there most of the time...
console.log("Got it!", this.test);
},
};
</script>
In that case, my view won't show the test data as Vue seems to hang on the systeminformation call indefinitely.
The console log won't even show as the await statement appears to make the mounted hook stuck.
I just tested systeminformation.getStaticData() function in my own Electron + Vue app and here's results:
It executes code asynchorously but the operation is so heavy, it makes the app almost completely unresponsive. It repeatedly spawns ~30 Node child processes effectively blocking 3-4 CPU threads.
I think it might be a bug with getStaticData() function. You might want to create a new issue in their repo and report it.
Solution
Either don't use this particular function and get all the needed info by running other systeminformation functions.
OR
Enable node integration in workers:
webPreferences: {
nodeIntegration: true,
nodeIntegrationInWorker: true
}
Execute this function in a worker, rather than on the main thread, and send the results back to your component, then terminate the worker. Though when you do this, open task manager and make sure it terminates all the spawned child processes with it:
Vue component
import sysInfoWorker from 'worker-loader!./workers/sysInfoWorker'
data () {
return {
sysInfoWorker: null
}
},
mounted () {
this.initSysInfoWorker()
},
methods: {
initSysInfoWorker () {
this.sysInfoWorker = new sysInfoWorker()
try {
this.sysInfoWorker.onmessage = (event) => {
console.log('sysInfoWorker message:', event)
this.test = event
}
this.sysInfoWorker.onerror = (error) => {
console.log('sysInfoWorker error:', error)
}
}
catch (error) {
console.log(error)
}
},
startSysInfoWorker () {
this.sysInfoWorker.postMessage({
operation: 'run:getStaticData'
})
},
cancelSysInfoWorker () {
this.sysInfoWorker.postMessage('cancel:getStaticData')
// this.sysInfoWorker.terminate()
}
}
Worker
const systeminformation = require('systeminformation')
const state = { cancelled: false }
// Listen to messages from parent thread
self.addEventListener('message', (event) => {
if (event.data === 'cancel:getStaticData') {
state.cancelled = true
}
else {
state.cancelled = false
initWorker(event)
}
})
async function initWorker (event) {
if (event.data.operation === 'run:getStaticData') {
const info = await systeminformation.getStaticData()
self.postMessage({ info: info })
}
}
If the worker throws errors, try adding the following to your vue.config:
module.exports = {
configureWebpack: {
module: {
rules: [
{
test: /\.worker\.js$/,
use: { loader: 'worker-loader' }
}

Remove Socket.IO emitter on disconnect from EventEmitter attatched event array of functions

I have EventEmitter and socket.io working in tandem and they work as I am expecting them to work. I have an object, that contains data, that data can randomly change at any point in time, when the data changes, it fires an EventEmitter.emit with the updated data.
In another file I have an EventEmitter.on which captures that data and passes it on to socket.emit to be sent to the front end, something like this:
Within the object containing the data:
UpdateIfDataIsNew(newData, oldData) {
if (newData !== oldData) { //simplified, not exactly like this
eventBus.emit('dataEvent', newData)
}
}
Where eventBus is a global EventEmitter object.
And in my file handling the sending of the data to the front end, the code is roughly:
ioServer.on("connection", (socket) => {
eventBus.on("dataEvent", (data) => {
socket.emit("frontEndDataEvent", data);
});
})
The way that this is working, whenever a new user connects, a new socket is created, and a new socket.emit is added to the list of functions to be fired when "dataEvent" is triggered, so that all connected users receive the updated data. The problem is that I don't know how to remove a particular socket.emit from the eventBus array of functions when a particular user disconnects, because when I don't do that, all functions stay in the event array, and all of them fire when an event is triggered, althought these users are no longer on the website, eventually the array gets incredibly big, even a simple refresh from a single user, keeps adding more and more functions to the array.
I know that I can see if a user has disconnected with socket.on("disconnect", ???) but then what?
http://prntscr.com/tou4pw the functions are the same, just with a different socket, how do I find the one that must be removed, when someone disconnects?
Instead of just adding a function to the eventEmitter, can I add say an object that contains an ID: and the function, so that I can then quickly identify the one that I must remove?
I solved it by creating my own Event Emitter class (externding EventEmitter), that way I have control over the array of functions for a given event and am also able to assign an ID.
const EventEmitter = require("events");
class RobotData extends EventEmitter {
constructor() {
super();
this.emitters = [];
}
add(socket, dataType) {
this.emitters.push({
socket,
dataType,
trigger: (eventName, newData) => {
socket.emit(`${eventName}`, newData);
},
});
}
remove(ID) {
this.emitters = this.emitters.filter((emitter) => emitter.socket.id !== ID);
}
}
const robotData = new RobotData();
module.exports = { robotData };
And then in the disconnect event:
socket.on("disconnect", () => {
robotData.remove(socket.id);
});

How to use Promise with redux and socket.io

I am a newbie to react-redux. I am stuck at situation wherein i emit an action to the server. The server in turn makes a third party API request which return a promise. My server.js file for handling socket connection in the server looks like the following
import Server from 'socket.io';
export default function startServer(store) {
const io = new Server().attach(8090);
store.subscribe(
() =>io.emit('curr_news', store.getState().toJS()));
io.on('connection', (socket) => {
socket.emit('curr_news', store.getState().toJS());
socket.on('action', store.dispatch.bind(store));
});
}
As you can see the action is what the client emits and the server makes an appropriate request when it receives the necessary action after which it emits the current state. The following is a sample reducer file
export default function reducer(curr_feeds = CURRENT_FEEDS, action) {
switch (action.type) {
case 'GET_TEST1DATA':
return getTest1data(curr_feeds);
case 'GET_TEST2DATA':
return getTest2data(curr_feeds);
}
}
here getTest1data and getTest2data essentially return a promise as it makes a request to some third part API. My problem is that as the the socket emits the curr_news immediately due to which the value of store.getState() is undefined.
My question is how do I make the store to observe and emit the socket once the promise resolves and not before that? Thanks for the help in advance.
When you subscribe to the store you gain a state whenever it updates. If that new state is a promise, you could process that:
Store.subscribe(state => state.then(promisedData => io.emit(promisedData))

Why is my RxJS Observable completing right away?

I'm a bit new to RxJS and it is kicking my ass, so I hope someone can help!
I'm using RxJS(5) on my express server to handle behaviour where I have to save a bunch of Document objects and then email each of them to their recepients. The code in my documents/create endpoint looks like this:
// Each element in this stream is an array of `Document` model objects: [<Document>, <Document>, <Document>]
const saveDocs$ = Observable.fromPromise(Document.handleCreateBatch(docs, companyId, userId));
const saveThenEmailDocs$ = saveDocs$
.switchMap((docs) => sendInitialEmails$$(docs, user))
.do(x => {
// Here x is the `Document` model object
debugger;
});
// First saves all the docs, and then begins to email them all.
// The reason we want to save them all first is because, if an email fails,
// we can still ensure that the document is saved
saveThenEmailDocs$
.subscribe(
(doc) => {
// This never hits
},
(err) => {},
() => {
// This hits immediately.. Why though?
}
);
The sendInitialEmails$$ function returns an Observable and looks like this:
sendInitialEmails$$ (docs, fromUser) {
return Rx.Observable.create((observer) => {
// Emails each document to their recepients
docs.forEach((doc) => {
mailer.send({...}, (err) => {
if (err) {
observer.error(err);
} else {
observer.next(doc);
}
});
});
// When all the docs have finished sending, complete the
// stream
observer.complete();
});
});
The problem is that when I subscribe to saveThenEmailDocs$, my next handler is never called, and it goes straight to complete. I have no idea why... Inversely if I remove the observer.complete() call from sendInitialEmails$$, the next handler is called every time and the complete handler in subscribe is never called.
Why isn't the expected behaviour of next next complete happening, instead it's one or the other... Am I missing something?
I can only assume that mailer.send is an asynchronous call.
Your observer.complete() is called when all the asynchronous calls have been launched, but before any of them could complete.
In such cases I would either make an stream of observable values from the docs array rather than wrap it like this.
Or, if you would like to wrap it manually into an observable, I suggest you look into the library async and use
async.each(docs, function(doc, callback) {...}, function finalized(err){...})

Resources