'Electron - Uncaught TypeError: Cannot read properties of undefined (reading 'showOpenDialog')

I'm trying to open the dialog on electron, but I'm getting this error:

Uncaught TypeError: Cannot read properties of undefined (reading 'showOpenDialog')

I've already visited several forums and communities to see if I have a solution to my problem. But none resolved perhaps because of the version.

The version of electron I currently use is 16.0.5

This answer didn't help me much

https://stackoverflow.com/a/63756725/14271847

I won't leave all of my main.js, but the part I changed is this one, enableRemoteModule:

  const mainWindow = new BrowserWindow({
    width: 800,
    height: 600,
    webPreferences: {
      enableRemoteModule:true,
      nodeIntegration: true,
      contextIsolation: false,
      preload: path.join(__dirname, 'preload.js')
    }
  })

file test.js

And here's what I'm trying to do on electron, following what's in this link

const { dialog } = require('electron')
console.log(dialog.showOpenDialog({ properties: ['openFile', 'multiSelections'] }))

I've tried it with the remote, but it doesn't work:

const { dialog } = require('electron')
console.log(dialog.remote.showOpenDialog({ properties: ['openFile', 'multiSelections'] }))

Could someone help please?



Solution 1:[1]

Enabling Context Isolation and disabling nodeIntegration is best practice nowadays to truly secure your Electron application. Additionally, the use of remote is discouraged as we now have Inter-Process Communication.

Not knowing what your preload.js script looks like, I have included a simple preload.js script below that uses a list of whitelisted channel names and implementation of ipcRenderer methods only. That said, having a different looking preload.js script should not change too much the core approach that should be used in your main.js and index.html files to get your dialog working.


Using of the invoke method will allow us to send a message to the main process to open the dialog and receive a reply, all in one.

preload.js (main process)

// Import the necessary Electron components.
const contextBridge = require('electron').contextBridge;
const ipcRenderer = require('electron').ipcRenderer;

// White-listed channels.
const ipc = {
    'render': {
        // From render to main.
        'send': [],
        // From main to render.
        'receive': [],
        // From render to main and back again.
        'sendReceive': [
            'dialog:openMultiFileSelect' // Channel name
        ]
    }
};

// Exposed protected methods in the render process.
contextBridge.exposeInMainWorld(
    // Allowed 'ipcRenderer' methods.
    'ipcRender', {
        // From render to main.
        send: (channel, args) => {
            let validChannels = ipc.render.send;
            if (validChannels.includes(channel)) {
                ipcRenderer.send(channel, args);
            }
        },
        // From main to render.
        receive: (channel, listener) => {
            let validChannels = ipc.render.receive;
            if (validChannels.includes(channel)) {
                // Deliberately strip event as it includes `sender`.
                ipcRenderer.on(channel, (event, ...args) => listener(...args));
            }
        },
        // From render to main and back again.
        invoke: (channel, args) => {
            let validChannels = ipc.render.sendReceive;
            if (validChannels.includes(channel)) {
                return ipcRenderer.invoke(channel, args);
            }
        }
    }
);

A summary on the use of the above preload.js script is shown below.

/**
 * Render --> Main
 * ---------------
 * Render:  window.ipcRender.send('channel', data); // Data is optional.
 * Main:    electronIpcMain.on('channel', (event, data) => { methodName(data); })
 *
 * Main --> Render
 * ---------------
 * Main:    windowName.webContents.send('channel', data); // Data is optional.
 * Render:  window.ipcRender.receive('channel', (data) => { methodName(data); });
 *
 * Render --> Main (Value) --> Render
 * ----------------------------------
 * Render:  window.ipcRender.invoke('channel', data).then((result) => { methodName(result); });
 * Main:    electronIpcMain.handle('channel', (event, data) => { return someMethod(data); });
 *
 * Render --> Main (Promise) --> Render
 * ------------------------------------
 * Render:  window.ipcRender.invoke('channel', data).then((result) => { methodName(result); });
 * Main:    electronIpcMain.handle('channel', async (event, data) => {
 *              return await promiseName(data)
 *                  .then(() => { return result; })
 *          });
 */

In the main.js script, using the handle method, let's listen for a message on the dialog:openMultiFileSelect channel. Once received, open the dialog.showOpenDialog() method and .then wait for a result. Once a result has been received (IE: Files selected and dialog accepted), return the result (via the handle method) back to the render process.

main.js (main process)

const electronApp = require('electron').app;
const electronBrowserWindow = require('electron').BrowserWindow;
const electronDialog = require('electron').dialog;
const electronIpcMain = require('electron').ipcMain;

const nodePath = require("path");

// Prevent garbage collection
let window;

function createWindow() {
    const window = new electronBrowserWindow({
        x: 0,
        y: 0,
        width: 800,
        height: 600,
        show: false,
        webPreferences: {
            nodeIntegration: false,
            contextIsolation: true,
            preload: nodePath.join(__dirname, 'preload.js')
        }
    });

    window.loadFile('index.html')
        .then(() => { window.show(); });

    return window;
}

electronApp.on('ready', () => {
    window = createWindow();
});

electronApp.on('window-all-closed', () => {
    if (process.platform !== 'darwin') {
        electronApp.quit();
    }
});

electronApp.on('activate', () => {
    if (electronBrowserWindow.getAllWindows().length === 0) {
        createWindow();
    }
});

// ---

electronIpcMain.handle('dialog:openMultiFileSelect', () => {
    let options = {
        properties: ['openFile', 'multiSelections']
    };

    return electronDialog.showOpenDialog(window, options)
        .then((result) => {
            // Bail early if user cancelled dialog
            if (result.canceled) { return }

            return result.filePaths;
        })
})

Finally, in the index.html file, we listen for a click event on the button and then send a message via the dialog:openMultiFileSelect channel to open the dialog (in the main process). Once a result is returned, .then display the selected file(s).

Note how we detect a result of undefined and manage it accordingly. This occurs when the user cancels the dialog.

index.html (render process)

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Electron Test</title>
        <meta http-equiv="Content-Security-Policy" content="script-src 'self' 'unsafe-inline';"/>
    </head>

    <body>
        <input type="button" id="button" value="Open File Dialog">

        <ul id="paths"></ul>
    </body>

    <script>
        document.getElementById('button').addEventListener('click', () => {
            window.ipcRender.invoke('dialog:openMultiFileSelect')
                .then((paths) => {
                    if (paths === undefined) { return } // Dialog was cancelled

                    let result = '';

                    for (let path of paths) {
                        result += '<li>' + path + '</li>';
                    }

                    document.getElementById('paths').innerHTML = result;
                })
        })
    </script>
</html>

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 midnight-coding