'electron v10.1.1 gives Uncaught TypeError: Cannot read property 'dialog' of undefined, but same code works in electron v9.3.0

I am trying to upload a file in an electron app which works perfectly for electron v9.3.0 but as soon as I use electron v10.1.1, it gives the following error Uncaught TypeError: Cannot read property 'dialog' of undefined at this line const dialog = electron.remote.dialog; see the screenshot below. enter image description here

The content of main.js is as below

const { app, BrowserWindow } = require('electron') 

function createWindow () { 
// Create the browser window. 
const win = new BrowserWindow({ 
    width: 800, 
    height: 600, 
    webPreferences: { 
    nodeIntegration: true
    } 
}) 

// Load the index.html of the app. 
win.loadFile('src/index.html') 

// Open the DevTools. 
win.webContents.openDevTools() 
} 

// This method will be called when Electron has finished 
// initialization and is ready to create browser windows. 
// Some APIs can only be used after this event occurs. 
// This method is equivalent to 'app.on('ready', function())' 
app.whenReady().then(createWindow) 

// Quit when all windows are closed. 
app.on('window-all-closed', () => { 
// On macOS it is common for applications and their menu bar 
// To stay active until the user quits explicitly with Cmd + Q 
if (process.platform !== 'darwin') { 
    app.quit() 
} 
}) 

app.on('activate', () => { 
// On macOS it's common to re-create a window in the 
// app when the dock icon is clicked and there are no 
// other windows open. 
if (BrowserWindow.getAllWindows().length === 0) { 
    createWindow() 
} 
}) 

// In this file, you can include the rest of your 
// app's specific main process code. You can also 
// put them in separate files and require them here. 

The content of index.js is as below

const electron = require('electron'); 
const path = require('path'); 

// Importing dialog module using remote 
const dialog = electron.remote.dialog;

var uploadFile = document.getElementById('upload'); 

// Defining a Global file path Variable to store 
// user-selected file 
global.filepath = undefined; 

uploadFile.addEventListener('click', () => { 
// If the platform is 'win32' or 'Linux' 
    if (process.platform !== 'darwin') { 
        // Resolves to a Promise<Object> 
        dialog.showOpenDialog({ 
            title: 'Select the File to be uploaded', 
            defaultPath: path.join(__dirname, '../assets/'), 
            buttonLabel: 'Upload', 
            // Restricting the user to only Text Files. 
            filters: [ 
                { 
                    name: 'Text Files', 
                    extensions: ['txt', 'docx'] 
                }, ], 
            // Specifying the File Selector Property 
            properties: ['openFile'] 
        }).then(file => { 
            // Stating whether dialog operation was 
            // cancelled or not. 
            console.log(file.canceled); 
            if (!file.canceled) { 
            // Updating the GLOBAL filepath variable 
            // to user-selected file. 
            global.filepath = file.filePaths[0].toString(); 
            console.log(global.filepath); 
            } 
        }).catch(err => { 
            console.log(err) 
        }); 
    } 
    else { 
        // If the platform is 'darwin' (macOS) 
        dialog.showOpenDialog({ 
            title: 'Select the File to be uploaded', 
            defaultPath: path.join(__dirname, '../assets/'), 
            buttonLabel: 'Upload', 
            filters: [ 
                { 
                    name: 'Text Files', 
                    extensions: ['txt', 'docx'] 
                }, ], 
            // Specifying the File Selector and Directory 
            // Selector Property In macOS 
            properties: ['openFile', 'openDirectory'] 
        }).then(file => { 
            console.log(file.canceled); 
            if (!file.canceled) { 
            global.filepath = file.filePaths[0].toString(); 
            console.log(global.filepath); 
            } 
        }).catch(err => { 
            console.log(err) 
        }); 
    } 
}); 

The content of index.html is as below

<!DOCTYPE html> 
<html> 
<head> 
    <meta charset="UTF-8"> 
    <title>Hello World!</title> 
    <!-- https://electronjs.org/docs/tutorial 
                        /security#csp-meta-tag -->
    <meta http-equiv="Content-Security-Policy"
        content="script-src 'self' 'unsafe-inline';" /> 
</head> 
<body> 
    <h1>Hello World!</h1> We are using node 
    <script> 
        document.write(process.versions.node) 
    </script>, Chrome 
    <script> 
        document.write(process.versions.chrome) 
    </script>, and Electron 
    <script> 
        document.write(process.versions.electron) 
    </script>. 

    <h3>File Upload in Electron</h3> 
    <button id="upload">Upload File</button> 
    
    <!-- Adding Individual Renderer Process JS File -->
    <script src="index.js"></script> 
</body> 
</html> 


Solution 1:[1]

as @tpikatchu stated:

const win = new BrowserWindow({ 
    width: 800, 
    height: 600, 
    webPreferences: { 
         enableremotemodule: true,
         nodeIntegration: true
    } 
})

but enableremotemodule: true
has to be in camelCase: enableRemoteModule: true

reference: https://www.electronjs.org/docs/api/browser-window

p.s: Sorry for the new answer creation, but I can't comment yet.

Solution 2:[2]

const win = new BrowserWindow({ 
    width: 800, 
    height: 600, 
    webPreferences: { 
         enableRemoteModule: true,
         nodeIntegration: true
    } 
}) 

In order to access the remote module on the renderer process. We need to enable enableRemoteModule as true as this is default false from the newer version.

Solution 3:[3]

For Electron 11.0 and above

The remote module has been deprecated. This means that the dialog object, needed in order to update a file, is not accessible from a a renderer javascript file (such as index.js in this post). The dialog object is still accessible from the main entry point. In order to fix this, you can use the ipcMain and ipcRenderer objects to manage communication between entry point and renderer javascript code.

in main.js - or the entrypoint used in your app - add the following:

const {app, BrowserWindow, dialog, ipcMain } = require('electron')

// *** REST OF YOUR CODE GOES HERE *** 

ipcMain.on('file-request', (event) => {  
  // If the platform is 'win32' or 'Linux'
  if (process.platform !== 'darwin') {
    // Resolves to a Promise<Object>
    dialog.showOpenDialog({
      title: 'Select the File to be uploaded',
      defaultPath: path.join(__dirname, '../assets/'),
      buttonLabel: 'Upload',
      // Restricting the user to only Text Files.
      filters: [ 
      { 
         name: 'Text Files', 
         extensions: ['txt', 'docx'] 
      }, ],
      // Specifying the File Selector Property
      properties: ['openFile']
    }).then(file => {
      // Stating whether dialog operation was
      // cancelled or not.
      console.log(file.canceled);
      if (!file.canceled) {
        const filepath = file.filePaths[0].toString();
        console.log(filepath);
        event.reply('file', filepath);
      }  
    }).catch(err => {
      console.log(err)
    });
  }
  else {
    // If the platform is 'darwin' (macOS)
    dialog.showOpenDialog({
      title: 'Select the File to be uploaded',
      defaultPath: path.join(__dirname, '../assets/'),
      buttonLabel: 'Upload',
      filters: [ 
      { 
         name: 'Text Files', 
         extensions: ['txt', 'docx'] 
      }, ],
      // Specifying the File Selector and Directory 
      // Selector Property In macOS
      properties: ['openFile', 'openDirectory']
    }).then(file => {
      console.log(file.canceled);
      if (!file.canceled) {
      const filepath = file.filePaths[0].toString();
      console.log(filepath);
      event.send('file', filepath);
    }  
  }).catch(err => {
      console.log(err)
    });
  }
});

Replace your code in index.js for:

const { ipcRenderer } = require('electron');
var uploadFile = document.getElementById('upload');

//upon clicking upload file, request the file from the main process
uploadFile.addEventListener('click', () => {
  ipcRenderer.send('file-request');
});

//upon receiving a file, process accordingly
ipcRenderer.on('file', (event, file) => {
  console.log('obtained file from main process: ' + file);
});

NOTE: I am using a asynchronous events. This can be made synchronous by using ipcRenderer.sendSync and processing a return value - check the electron documentation for more information. The difference between the two is that sendSync is a synchronous promise: it blocks the window until a return value is issued by ipcMain. This may seem desiarable for a process like this as we may want the window to wait for the file to be uploaded until allowing the user to continue interacting. I did not do it that way because:

  • It would block the whole app if unproperly unhandled, as explained in the electron documentation
  • Even if the app is blocked on sendSync, the button clicks are processed. Once the file dialogue is closed, the app will fire all responses of the clicks that have been made, so the blockage is not that useful.
  • It is possible to manage whether the dialogue is open or not on the renderer javascript by using a simple bool and managing the cancel option - happy to provide this if anyone needs it!

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Melas
Solution 2
Solution 3 Diana Vallverdu