Reputation: 509
I am using Electron 13 with React 17. I have set nodeIntegration
to false and contextIsolation
to true, therefore I am using a preload.js
file to expose an API to communicate between the main and renderer processes.
I must listen to IPC messages in a React component using this API. However, each time my component is mounted (or re-rendered), Electron creates a new IPC event listener, causing a memory leak.
preload.js
const { contextBridge, ipcRenderer } = require('electron');
contextBridge.exposeInMainWorld('api', {
send: (channel, data) => {
// Whitelist channels
let validChannels = ['toMain'];
if (validChannels.includes(channel)) {
ipcRenderer.send(channel, data);
}
},
receive: (channel, func) => {
let validChannels = ['fromMain'];
if (validChannels.includes(channel)) {
// Deliberately strip event as it includes `sender`
ipcRenderer.on(channel, (event, ...args) => func(...args));
}
}
});
ReactComponent.js
import { useEffect, useRef } from 'react';
const ReactComponent = () => {
const _isMounted = useRef(true);
const exampleFunction = () => {
window.api.send('toMain');
};
window.api.receive('fromMain', function (data) {
if (_isMounted.current) {
// Process `data`...
}
});
useEffect(() => {
exampleFunction();
// Another IPC call
window.api.send('toMain', ['example']);
window.api.receive('fromMain', function (data) {
// Process `data`...
});
return () => {
_isMounted.current = false;
// Somehow I should remove the IPC event listeners here,
// but I don't know how, since (I think) they are created
// in the `preload.js` file...
};
}, []);
return (
// JSX
);
};
export default ReactComponent;
How can I unregister the event listeners created through window.api.receive()
?
Upvotes: 1
Views: 2883
Reputation: 509
This issue on GitHub addresses such problems exactly. In short, the event listener created in preload.js
is assigned to a variable so the script can return a callback to remove the event listener.
Here is an example of how this may be executed:
preload.js
const { contextBridge, ipcRenderer } = require('electron');
contextBridge.exposeInMainWorld('api', {
// ...
receive: (channel, func) => {
let validChannels = ['fromMain'];
if (validChannels.includes(channel)) {
// Deliberately strip event as it includes `sender`
const subscription = (event, ...args) => func(...args);
ipcRenderer.on(channel, subscription);
return () => {
ipcRenderer.removeListener(channel, subscription);
};
}
},
});
ReactComponent.js
import { useEffect } from 'react';
const ReactComponent = () => {
const onEvent = (data) => {
// Process `data`...
};
useEffect(() => {
const removeEventListener = window.api.receive('fromMain', (data) => onEvent(data));
// ...
return () => {
removeEventListener();
};
}, []);
return (
// JSX
);
};
export default ReactComponent;
Upvotes: 12