How to import ipcRenderer in Renderer Process’ Component

If you’re already working on a project that uses electron and you were not a part of the development from the beginning, then you might find yourselves in the same shoes that I found myself when I decided to write an app in Electron all the way from scratch.

Doesn’t seem like such a difficult thing right? That’s what I thought too. So I quickly created a sample web app using create-react-app and started updating the UI to suit my sample application. It was all going well, I had encapsulated my app with electron and my simple web page had become a simple electron app. Life was simple. Then I decided to take it further by trying to send messages from renderer process to main process and back.

My first attempt to do so was (as most of you would have done I hope) by checking the application on which I was working earlier and figuring out how my predecessor had done it. So I went ahead, implemented it, and just as I had thought it didn’t work. And five minutes later I was googling How to import ipcRenderer in Renderer Process.

But I’m going through countless StackOverflow questions, Google Groups discussions, Medium articles, and seeing a bunch of different ways but nothing which would work in my favor. So in this article, I’ll take you through my journey of solving this problem.

I’m going to tell you about four different ways of importing ipcRenderer inside your app component.

Importing Electron

One of the easiest ways to do this is by directly importing electron inside your App Component. You can do so with the following command.

const ipcRenderer = window.require('electron').ipcRenderer;

While this is an easy way but it may result in an error as you are not allowed to use window.require inside of renderer process in the newer versions of electron. Your error would look something like this -

Uncaught ReferenceError: require is not defined at main_window.js:1

Now you can fix this by tweaking the webPreferences of your BrowserWindow like so

new BrowserWindow({
width: 650,
height: 550,
webPreferences: {
nodeIntegration: true
}
})

Even though this may solve the problem, electron docs suggest another way covered in the next section.

Preload Script on Renderer Process

You can expose ipcRenderer as an attribute of window variable for your renderer process by writing a preload script while creating a new BrowserWindow inside your main process.

Preload Script

Create a file preload.js at the same location as the entry point for your electron app. Add the line below to your preload script.

window.ipcRenderer = require('electron').ipcRenderer;

Adding Preload Script To Browser Window

To load the script before creating a renderer process, you need to pass preload.js into the webPreferences option of BrowserWindow method.

main_window = new BrowserWindow({
width: 650,
height: 550,
webPreferences: {
preload: path.join(app.getAppPath(), 'preload.js')
}
})

You can import ipcRenderer in App Component as an attribute of the window.

const ipcRenderer = window.ipcRenderer;

Exposing ipcRenderer via Context Bridge

Context Bridge gives you a safe way of exposing APIs that are accessible in the main process to the renderer process and vice versa. You can read more about Context Bridge and the Isolated World in the electron docs. You can use the exposeInMainWorld method of ContextBridge to expose ipcRenderer to the Renderer Process

Syntax

contextBridge.exposeInMainWorld(apiKey, api)

apiKey String - The key to inject the API onto window with. The API will be accessible on window[apiKey].

api any - The API provided to exposeInMainWorld must be a Function, String, Number, Array, Boolean, or an object whose keys are strings and values are a Function, String, Number, Array, Boolean, or another nested object that meets the same conditions.

Exposing ipcRenderer

This is what your preload.js would like with ContextBridge.

const {ipcRenderer, contextBridge} = require('electron');contextBridge.exposeInMainWorld("ipcRenderer",ipcRenderer)

By doing this you expose the ipcRenderer via Context Bridge and it can be imported into your App component by simply writing

const { ipcRenderer } = window;

The problem I faced by doing this was while ipcRenderer was available in the Renderer Process with all the methods but the events were missing from them. And you can definitely find a workaround for this but the question you need to ask yourself here is whether you just want your code to work now and locally or always and everywhere? Well for those who believe in doing things the right way then check out the next section.

Exposing methods of ipcRenderer via Context Bridge

In the previous section, we exposed the entire ipcRenderer. But like in the previous blog on How to download any file to custom location we just needed the send method and on event listener. So why not expose these two instead of the entire ipcRenderer object. We just have to update the exposeInMainWorld method in our preload.js script.

const {ipcRenderer, contextBridge} = require('electron');contextBridge.exposeInMainWorld("api",{
send: (channel, data) => ipcRenderer.send(channel, data),
recieve: (channel, func) => ipcRenderer.on(
channel,
(event, ...args) => func(args)
)
})

Now we have exposed the ipcRenderer.send method as send & ipcRenderer.on event as receive method in our api. So in our renderer process, we'll use it as follows

const { api } = window;
...
useEffect(() => {
// ipcRenderer.on becomes api.recieve
api.recieve("download-progress", (args) => {
//handle on download-progress event
});
}, []);
...
//To send message to Main Process
const download = (url) => {
//ipcRenderer.send becomes api.send
api.send("download", {
payload: {
url
}
});
}

Summary

Bi-Directional Communication between the main process and the renderer process seems very basic when you’re starting developing apps with electron. But it caused me a great deal of trouble when I started implementing things. So this is an attempt to make the next dev’s life easier.

I personally feel that a developer has much more control over the app by only exposing the required methods and events using Context Bridge. Let me know in the comments below if I missed any other super-easy way to do this, till then keep experimenting with new things and sharing them.

Front-end enthusiast | App developer | MTS at Lambdatest