48

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)

Wor Chan
  • 139
  • 1
  • 11
relidon
  • 2,142
  • 4
  • 21
  • 37

5 Answers5

78

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

Joshua
  • 5,032
  • 2
  • 29
  • 45
  • If you want to send a direct message and doesn't have access to the Event, This works! :) – Krunal Panchal Dec 14 '19 at 12:26
  • 3
    If you send the message, like for example right after reloading the window, wrap the message sending in `win.webContents.once('did-finish-load', () => { ... })` – Aryo Dec 15 '19 at 05:53
  • I tried this, but got the EventEmitter warning about a possible memory leak. Is there any way to clean up the previous events before emitting a new event? – Yamo93 Jun 13 '21 at 07:46
  • @Yamo93 I'm not exactly sure what you mean, can you post a new question with all the details? – Joshua Jun 13 '21 at 07:53
  • @Joshua I posted this question now: https://stackoverflow.com/questions/67956184/memory-leak-when-emitting-an-event-after-menu-click-in-electron-app – Yamo93 Jun 13 '21 at 08:14
  • Recomend [this solution](https://stackoverflow.com/questions/57807459/how-to-use-preload-js-properly-in-electron/69917666#69917666) by @abel-callejo, where ipcRenderer need not be exposed to renderer. – heLomaN Jan 12 '22 at 08:34
  • I have same problem , I have 3 files main.js , app.js and savinimages.js. Main Application Running on main.js and html file included. app.js running server and receiving data. I am able to Communicate between main.js and app.js. I want to open new window based on button click(new window having another html file and savingimages.js included in html file) and I want to show {"apple" : "mango"} value. How to communicate main.js to saveimages.js or app.js to savingimages.js – Amarnath Reddy Surapureddy Apr 27 '23 at 04:40
19

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

Pawel Wrobel
  • 494
  • 3
  • 13
  • 28
    But this does NOT work if you want the MainProcess to initiate the first message by sending the first message to the Renderer... – Goku Jan 31 '20 at 00:23
  • 7
    This works for the following flow: **Renderer** › **Main** › **Renderer**. Although the question is about the following flow: **Main** › **Renderer** – Abel Callejo Apr 02 '21 at 00:41
15

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);
Ambroise Rabier
  • 3,636
  • 3
  • 27
  • 38
1

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);
});
raksa
  • 898
  • 6
  • 17
  • Interesting solution, but I don't think it's well explained. Correct me if I'm wrong, I think it's essentially long-polling: you are sending a message to Main, that sticks around (it's async after all). When Main needs to send a message to Renderer, it replies to the previous message that was hanging around there, and then Renderer, upon receiving the reply, should reply a new message that will lie waiting with Main until Main wants to send a message back to Renderer. If that's right, then the code should better reflect that. I'm also not sure if there can be some sort of time-out issue – flen Apr 17 '22 at 02:33
1

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)
  })
Johnson Fashanu
  • 897
  • 8
  • 6
  • Little off-topic, but we are supposed to use preload approach by new standards, or am I missing something? (since this approach is 2 months old, but ignores preload..) – Jake_3H Nov 19 '22 at 00:04