How can we send messages from the main process to renderer process in Electron

Viewed 45714

I'm playing with electron for the first time. Trying to create a text editor

In render I'm sending a message to indicated the content has changed and needs saving:

document.getElementById('content').onkeyup = e => {
  ipcRenderer.send('SAVE_NEEDED', {
    content: e.target.innerHTML,
    fileDir
  })
}

Then ipcMain receives it no problem. On the menu I have this:

{
  label: 'Save',
  click: _ => {
     saveFile(message)
     // trying:
     // ipcMain.send('SAVED', 'File Saved')
     },
     accelerator: 'cmd+S', // shortcut
}

So that the user knows the files has have. But that doesn't seem to work. Is there any other way to do this? I would have thought "save" would be a pre-created role (sort of)

5 Answers

To send a message back to the renderer you would use:

win.webContents.send('asynchronous-message', {'SAVED': 'File Saved'});

And receive it like this:

ipcRenderer.on('asynchronous-message', function (evt, message) {
    console.log(message); // Returns: {'SAVED': 'File Saved'}
});

Where asynchronous-message is simply the channel you're sending it to. It can literally be anything.

webContents.send Docs

alternatively - when you want to respond to an event received from renderer process you can do something like this:

     ipcMain.on("eventFromRenderer", (event) => {
          event.sender.send("eventFromMain", someReply);
     }

Source: https://electronjs.org/docs/api/ipc-main

Here is what I tinkered with (using the new way of doing with contextBridge), my use was simply to have a menuItem call a navigation event in my React code:

// preload.js

const exposedAPI = {
  // `(customData: string) => void` is just the typing here
  onMenuNav: (cb: (customData: string) => void) => {
    // Deliberately strip event as it includes `sender` (note: Not sure about that, I partly pasted it from somewhere)
    // Note: The first argument is always event, but you can have as many arguments as you like, one is enough for me.
    ipcRenderer.on('your-event', (event, customData) => cb(customData));
  }
};

contextBridge.exposeInMainWorld("electron", exposedAPI);
// typing for curious peoples
onMenuNav(cb: ((customData: string) => void)): void;
// renderer.tsx
// Call it in the renderer process, in my case it is called once at the end of renderer.tsx.

window.electron.onMenuNav(customData => {
  console.log(customData); // 'something'
});
// in main process

const customData = 'something';
mainWindow.webContents.send('your-event', customData);

Old question but I found new good solution;

// on main process index.js
ipcMain.on('event-name', (event, data) => {
    const value = 'return value';
    event.reply(data.waitingEventName, value);
});

// on render frame index.html
const send =(callback)=>{
    const waitingEventName = 'event-name-reply';
    ipcRenderer.once(waitingEventName, (event, data) => {
        callback(data);
    });
    ipcRenderer.send('event-name', {waitingEventName});
};
send((value)=>{
    console.log(value);
});

From @raksa answer

You can send a request to main and get a response, rather than sending a response that might not be delivered.

Use this sample

//render process
ipcRenderer.send('hello', ['one', 'two', 'three']);

ipcRenderer.once('nice', (e, data) => {
  console.log(data); //['one','two','three']
}) 

//main process
  ipcMain.on('hello', (e, data) => {
    e.reply('nice', data)
  })
Related