Create a Desktop App Using Electron and React

Ashish Srivastava
Bobble Engineering
Published in
5 min readAug 28, 2020

A basic guide to start using Electron with React in minimum steps

Electron says that if you can build a website, you can build a cross-platform desktop app. Our beloved code editor VS Code uses electron, so does Whatsapp Desktop App. Electron uses Node.js runtime and Chromium rendering engine to build apps using HTML, CSS and JavaScript.

Why React

React is an open-source JavaScript library for building user interfaces or UI components. Both functional(hooks) and class component approach is fun to learn. React is declarative and component based.

Create-React-App

To start building a new single page React application, React Community provides a boilerplate application to jump start app development.

Note: Make sure you have Node.js installed in your system

Creating our React Application

With your CLI pointing to the folder you want to store your app, run

npx create-react-app electron-react-demo

which creates a new react application named electron-react-demo with required boilerplates.

cd electron-react-demo
npm start

to start your React app on localhost.

You can now view electron-react-demo in the browser.
Local: http://localhost:3000

The folder structure of electron-react-demo will be:

electron-react-demo
├── README.md
├── node_modules
├── package.json
├── .gitignore
├── public
│ ├── favicon.ico
│ ├── index.html
│ └── manifest.json
└── src
│├── App.css
│├── App.js
│├── App.test.js
│├── index.css
│├── index.js
│├── logo.svg
│├── serviceWorker.js
└── setupTests.js

Installing Electron

We install Electron as a dev-dependency

npm install -D electron

In src folder we create a new file electron.js to launch the electron application

const electron = require('electron');
const app = electron.app;
const BrowserWindow = electron.BrowserWindow;
let mainWindow;function createWindow() {mainWindow = new BrowserWindow({
width: 1024,
height: 768,
webPreferences:{
nodeIntegration:true
}
});
mainWindow.loadURL('http://localhost:3000');mainWindow.webContents.openDevTools();mainWindow.on('closed', function () {
mainWindow = null
})
}app.on('ready', createWindow);app.on('window-all-closed', function () {
if (process.platform !== 'darwin') {
app.quit()
}
});
app.on('activate', function () {
if (mainWindow === null) {
createWindow()
}
});

Electron has two process:
1. Main process — electron.js launched on application start is the main process.
2. Renderer process — BrowserWindows created in main process can access the renderer APIs for UI and connectivity.

The app API is used to control the event lifecycle of the application and creates window on event ‘ready’. BrowserWindow API is used to create and control browser windows. createWindow creates a new window with specified height and width and other relevant specifications. Open Chrome Developer tools using openDevTools(). Browser Window created listens for a ‘closed’ event to close the window. Browser Windows loads the localhost port on which the React app is running.

Launching React Application in Electron

  1. Add an entry point to Electron

We add a main entry point in package.json, which holds all the metadata related to the project.

"main": "src/electron.js"

Also, to run electron we add a run target to Electron in package.json,

"electron":"electron ."

2. Running the below commands separately opens an Electron application with create-react-app content. We can make changes in App.js to edit the UI.

npm start
npm run electron

Here, we have created an Desktop application with minimum effort for development.
However, both React dev server and Electron must be launched separately and must be run in such a way that Electron is launched after React scripts are loaded.

3. Using Foreman to simplify application launch

Foreman is a process management tool, we add it using

npm install -D foreman

We create a Procfile to manage the launch of two process i.e. React webpack-dev-server and electron as:

react:npm start
electron:npm run electron

This solves our problem of separately launching the two processes. We add a new file named wait-for-react.js to deal with our issue waiting for React dev server to start and then launching electron.

// wait-for-react.js
const net = require('net');
const port = process.env.PORT ? (process.env.PORT - 100) : 3000;
process.env.ELECTRON_START_URL = `http://localhost:${port}`;const client = new net.Socket();let startedElectron = false;
const tryConnection = () => client.connect({port: port}, () => {
client.end();
if(!startedElectron) {
console.log('starting electron');
startedElectron = true;
const exec = require('child_process').exec;
exec('npm run electron');
}
}
);
tryConnection();client.on('error', (error) => {
setTimeout(tryConnection, 1000);
});

This simple node script checks if the React dev has started and then launches the electron process. We modify the earlier added Procfile

react:npm start
electron:npm run src/wait-for-react.js

4. Further, we make some changes to our package.json file:

  1. Since, create-react-app builds an index.html that uses absolute paths by default. So, it fails to load it in Electron. However, there is a config option to change this, to set a homepage property in package.json. We set this property in current directory and npm run build will use this as a relative path.
"homepage":"./"

2. npm start should launch our application as desired. However, currently this only launches the React dev server. Therefore, we add the following changes to package.json and Procfile respectively,

“start”: “nf start -p 3000”, 
“electron-start”:”node src/wait-for-react”,
“react-start”:”react-scripts start”

Final Procfile,

react: npm run react-start
electron: npm run electron-start

These two complimentary changes, enables us to use npm startto launch the application. Also, we add a .env file with BROWSER=none in source directory to stop the React dev server to start in browser. Finally our package.json file is as

{
"name": "electron-react-demo",
"version": "0.1.0",
"private": true,
"dependencies": {
"@testing-library/jest-dom": "^4.2.4",
"@testing-library/react": "^9.5.0",
"@testing-library/user-event": "^7.2.1",
"react": "^16.13.1",
"react-dom": "^16.13.1",
"react-scripts": "3.4.3"
},
"scripts": {
"start":"nf start -p 3000",
"react-start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject",
"electron": "electron ."
},
"eslintConfig": {
"extends": "react-app"
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
},
"homepage":"./",
"devDependencies": {
"electron": "^10.0.0",
"foreman": "^3.0.1"
},
"description": "This is a project demonstrating create-react-app with electron.",
"main": "src/electron.js",
"keywords": [
"electron",
"react"
],
"author": "*******@bobble.ai",
"license": "ISC"
}

Now, run npm start and there, our electron app with React Front end is running.

Our Electron Application with Dev Tools opened

To access the file system or to use the Electron’s ipcRenderer, we need to import Electron in the React app with the following workaround,

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

In development, an environment variable ELECTRON_START_URL can specify the URL for mainWindow.loadURL (in electron.js).

In production, if the env var exists we use it, else we load the static index.html file.

const startUrl = process.env.ELECTRON_START_URL || url.format({
pathname: path.join(__dirname, '/../build/index.html'),
protocol: 'file:',
slashes: true
});
mainWindow.loadURL(startUrl);
Final directory structure

Coming soon

Electron packs a lot of APIs and has a lot to offer for developers e.g. creating frame-less Apps. Stay tuned for more exciting stories.

Thanks and Happy Coding!

--

--