Create a Desktop App Using Electron and React
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
- 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:
- 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 start
to 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.
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);
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!