Looky1173
Looky1173

Reputation: 509

Remove event listener from `preload.js` in Electron created by React component

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

Answers (1)

Looky1173
Looky1173

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

Related Questions