VueJS getting "undefined" data from ipcRenderer (ElectronJS)

Viewed 456

When trying to get a message from ipcMain to ipcRenderer (without node integration and with contextIsolation), it's received but as undefined. Not only that, but if I were to reload the VueComponent (regardless of what change I make to it), the number of responses gets doubled.

For example, the first time I start my application, I get 1x undefined at a time every time I click the button. If I reload the component, I start getting 2x undefined every time I click the button. I reload again and get 4x undefined every time I click the button... and it keeps doubling. If I restart the application, it goes back to 1x.

undefined

SETUP

ElectronJS + VueJS + VuetifyJS has been set up as described here.

preload.js as per the official documentation.

import { contextBridge, ipcRenderer } from 'electron'
window.ipcRenderer = ipcRenderer

// Expose protected methods that allow the renderer process to use
// the ipcRenderer without exposing the entire object
contextBridge.exposeInMainWorld('ipcRenderer', {
  send: (channel, data) => {
    // whitelist channels
    let validChannels = ['toMain']
    if (validChannels.includes(channel)) {
      ipcRenderer.send(channel, data)
    }
  },
  receive: (channel, func) => {
    let validChannels = ['fromMain']
    if (validChannels.includes(channel)) {
      // Deliberately strip event as it includes `sender`
      ipcRenderer.on(channel, (event, ...args) => func(...args))
    }
  }
})

background.js (main process) as per the official documentation for the preload.js file. The omitted code via ... is the default project code generated upon creation.

...

const path = require('path')
const { ipcMain } = require('electron')

async function createWindow() {
  // Create the browser window.
  const win = new BrowserWindow({

    width: 800,
    height: 600,
    webPreferences: {
      
      // Use pluginOptions.nodeIntegration, leave this alone
      // See nklayman.github.io/vue-cli-plugin-electron-builder/guide/security.html#node-integration for more info
      nodeIntegration: process.env.ELECTRON_NODE_INTEGRATION,
      contextIsolation: true,
      preload: path.join(__dirname, 'preload.js'),
    },
    icon: 'src/assets/icon.png',
  })

  ipcMain.on('toMain', (event, data) => {
    console.log(data)
    event.sender.send('fromMain', 'Hello IPC Renderer')
    // The two lines below return 'undefined' as well in the 'ipcRenderer'
    //win.webContents.send('fromMain', "Hello IPC Renderer")
    //event.reply('fromMain', 'Hello IPC Renderer')
  })

  ...
}

...

vue.config.js file:

module.exports = {
  ...
  pluginOptions: {
    electronBuilder: {
      preload: 'src/preload.js',
    }
  }
}

main.js (renderer process) contains only the default project code generated upon creation.

VueComponent.vue

<template>
  <div id="vue-component">
    <v-btn @click="sendMessageToIPCMain()">
  </div>
</template>

<script>
export default {
  name: "VueComponent",
  components: {
    //
  },
  data: () => ({
    myData: null,
  }),
  methods: {
    // This works. I get 'Hello IPC Main' in the CMD console.
    sendMessageToIPCMain() {
      var message = "Hello IPC Main"
      window.ipcRenderer.send("toMain", message);
    }
  },
  mounted() {
    window.ipcRenderer.receive('fromMain', (event, data) => {
      // this.myData = data // 'myData' is not defined error
      this.$refs.myData = data;
      console.log('myData variable: ' + this.$refs.myData) // undefined
      console.log(data) // undefined
    })
  },
}
</script>

The VueComponent.vue's mounted() has been set up as described here, though If I try to send the data to a variable using this.myData = data, I get an error saying that myData has not been defined - using this.$refs.myData works, though it's still undefined.

P.S. myData has not been defined error =/= undefined. The former is a proper error in red letters while the latter is as seen in the image above.

1 Answers

For solving the first problem (doubling of function calls) you have to remove window.ipcRenderer = ipcRenderer. In contextIsolation mode the approach is to use contextBridge.exposeInMainWorld() only. Using both implementation definitely causes issues.

For the second problem, the callback to receive in ipcRenderer is called with only ...args from main (no event passed to func). see:

ipcRenderer.on(channel, (event, ...args) => func(...args)) <-- func() is called with only args

The only thing you should change is your function in mounted, to accept only data:

window.ipcRenderer.receive('fromMain', (data) => {
  console.log(data) // should log you data
})
Related