electronelectron-react-boilerplate

Electron ipcRenderer listener not triggered in React component


I have a button that should trigger all windows to perform an action, play media.

I have a MediaProvider class in the main process that handles all communication for the media IPC channels (truncated for brevity). This class is initialized in main.ts.

export default class MediaProvider {

  public constructor() {

    ipcMain.on(MEDIA_CHANNELS.PLAY, async (e: Electron.IpcMainInvokeEvent) => {
      console.log('PLAY handler in ipcMain');
      const result = await this.playMedia();
      return result;
    });

    ...
  }

  private playMedia = () => {
    BrowserWindow.getAllWindows().forEach(window => {
      console.log(`Sending PLAY command to window ${window.id}.`)
      window.webContents.send(MEDIA_CHANNELS.PLAY);
    });
  }

  ...
}

My main component calls this on clicking the play button:

window.electron.ipcRenderer.send(MEDIA_CHANNELS.PLAY);

Which correctly triggers my ipcMain handler.

enter image description here

In another functional React component I have the following:

  useEffect(() => {
    initListeners();
  }, []);

  const initListeners = () => {
    window.electron.ipcRenderer.on(MEDIA_CHANNELS.PLAY, (_) => {
      console.log('PLAY in component');
      playMedia();
    });

    ...
  }

My preload.ts simply exposes ipcRenderer methods as-is:

contextBridge.exposeInMainWorld('electron', {
  ipcRenderer: {
    send: ipcRenderer.send,
    on: ipcRenderer.on,
    invoke: ipcRenderer.invoke
  },
});

The listener is never triggered, there is never a 'PLAY in component' log. What could be causing my listener to fail to trigger?

I tried rewriting the above component into a class component vs. functional, but it didn't fix anything. I also thought maybe the overlapping channel name between ipcMain and ipcRenderer was causing an issue, but I added a new channel name for testing and still could not get the listener to fire.


Solution

  • Since the asking of this question, the example preload.ts in the electron-react-boilerplate has been updated. https://github.com/electron-react-boilerplate/electron-react-boilerplate/blob/main/src/main/preload.ts

    After matching the example for .on(...) exactly, and without altering anything else, my handlers now work.

    ...,
    on(channel: MEDIA_CHANNELS, func: (...args: unknown[]) => void) {
      const subscription = (_event: IpcRendererEvent, ...args: unknown[]) =>
        func(...args);
      ipcRenderer.on(channel, subscription);
    
      return () => {
        ipcRenderer.removeListener(channel, subscription);
      };
    },
    ...