I have a button in my react application that needs to do something 1 time every time it's clicked. Currently, when clicked, the listener seems to multiply.
Here is brief example of the code that I'm using:
// In App.js
const App = () => {
const buttonHandler = () => {
api.myApi.rendererToMain();
api.myApi.mainToRenderer();
};
return (
<div>
<Button cb={buttonHandler} />
</div>
);
};
// In Button.js
const Button = (props) => {
return (
<button type="button" onClick={ (e) => { props.cb() }}>Send Ping</button>
)
};
// In main.js
ipcMain.on('async', (e, msg) => {
console.log(msg); // print ping
e.reply('async_response', 'pong');
});
// In preload.js
contextBridge.exposeInMainWorld('api', {
myApi: {
rendererToMain: () => {
ipcRenderer.send('async', 'ping');
},
mainToRenderer: () => {
ipcRenderer.on('async_response', (e, msg) => {
console.log(msg);
});
},
},
});
When I run the electron app, I have a terminal for the Main process open, and devTools open for output from the renderer. Currently, when the button is pressed 3 times, the result looks like:
// In the browserWindow devTools console
pong
(2)pong
(5)pong
// In the Main process console
ping
ping
ping
The desired output is only different for the renderer console:
pong
pong
pong
My attempted solution
My first attempt at solving this on my own, after some stackoverflow research, was to try and remove the ipcRenderer listener for the "async_response" channel. All attempts to do this resulted in no output in the renderer console, and all 3 expected pings in the Main process console.
For example:
// Change made in preload.js
contextBridge.exposeInMainWorld('api', {
myApi: {
rendererToMain: () => {
ipcRenderer.send('async', 'ping');
},
mainToRenderer: () => {
ipcRenderer.on('async_response', (e, msg) => {
console.log(msg); // With below change, this does nothing.
});
// Attempt to remove the listener now?
// Seems to remove the listener before any output logged.
ipcRenderer.removeAllListeners('async_response');
},
},
});
If anyone could help me understand where and how to stop the listeners from multiplying, I would be eternally grateful. Thank you in advance.
After some sleep, I realized that my search was too narrow. I found that the ipcRenderer.on() method was being subscribed to an additional time each time the button was pressed, as per the concepts in this post: Node .on method firing too many times
Understanding this, I made the following change to my code, such that the call to the ipcRenderer.on() method only occurred once:
// In App.js
const App = () => {
const buttonHandler = () => {
api.myApi.rendererToMain();
// api.myApi.mainToRenderer(); <- Removed this call to the ipcRenderer.on()
};
return (
<div>
<Button cb={buttonHandler} />
</div>
);
};
// In Button.js
const Button = (props) => {
const callbackHandler = () => {
props.cb();
};
// Subscribe to listener on component construction
api.myApi.mainToRenderer();
return (
<button type="button" onClick={ (e) => { callbackHandler() }}>Send Ping</button>
)
};
These changes result in exactly the expected behavior. After three clicks of the button, I see in the renderer console:
(3)pong
Going to leave this unanswered for a time. I'm definitely open to comments about my fix and implementation all around.