Create Your First React Desktop Application in Electron with Hot-Reload
Tags: javascript, webdev, react, node
Published: 2019-10-17 02:41:46

Find me on [medium](https://medium.com/@jsmanifest)
Join my [newsletter](https://app.getresponse.com/site2/javascript-newsletter?u=zpBtw&webforms_id=SM2hz)

If you're a JavaScript developer you might have had most (or _all_) of your experience building web applications especially with all of these hot new trending technologies being released in every corner within the JavaScript community over the past few years. This might have had an impact on what you decided to develop with over the years.

When we're surrounded by a constantly changing environment in the JavaScript ecosystem that is mostly involved with apps in the web, we might even question whether desktop applications are even worth to get into. A [stackoverflow survey](https://insights.stackoverflow.com/survey/2019#developer-roles) for 2019 revealed that there is still a good percentage of desktop developers out there ranging to about 21% of 90,0000 survey participants that are developers, worldwide.

In other words, developing apps in the desktop are still an ongoing popular choice. And so if you were wondering on how to begin with creating your first modern desktop application using JavaScript, then I hope this post will help you to get started with writing your own desktop software application!

We will be using [Electron](https://electronjs.org) as our main tool. We will also be installing [React](https://reactjs.org/) as we will be using it to get started with building our user interfaces.

The first thing we are going to do is to create our project using [create-react-app](https://github.com/facebook/create-react-app) which will be used to provide react and some other useful tools like [Jest](https://jestjs.io/docs/en/getting-started) for running tests. For this tutorial, I will call our project `electron-react-typescript-app`:

If you want to grab your copy of the repo, visit [this link](https://github.com/jsmanifest/electron-react-app)

```shell
npx create-react-app electron-react-typescript-app`
```

Running that will create the **electron-react-typescript-app** folder and install the necessary dependencies listed in `package.json`.

Now let's go ahead and clean up the files we won't be needing. This is how my directory ended up looking like:

![cleanup1.jpg](https://jsmanifest.s3-us-west-1.amazonaws.com/posts/create-your-first-react-desktop-application-in-electron-with-hot-reload/cleanup1.jpg)

And here is our `App.js` component:

```jsx
import React from 'react'

function App() {
  return <h1>Our Electron App</h1>
}

export default App
```

Now we will go ahead and install `electron` as a dependency:

```shell
npm i electron
```

And then we will install [electron-builder](https://github.com/electron-userland/electron-builder), a complete solution to package and build a ready for distribution Electron app with _auto update_ support out of the box.

Install it as a _dev dependency_:

```shell
npm i -D electron-builder
```

Note: `-D` is just an alias for `--save-dev`

Go ahead and create a `"build"` property in _package.json_ since `electron-builder` will be using that:

```js
{
  "name": "electron-react-typescript-app",
  "version": "0.1.0",
  "private": true,
  "homepage": "./",
  "build": {
    "appId": "some.id.ofyours",
    "directories": {
      "buildResources": "assets"
    },
    "win": {
      "category": "your.app.category.type",
      "iconUrl": "path-to-icon.png"
    },
    "mac": {
      "category": "your.app.category.type",
      "iconUrl": "path-to-icon.png"
    }
  },
  "dependencies": {
    "electron": "^6.0.12",
    "react": "^16.10.2",
    "react-dom": "^16.10.2",
    "react-scripts": "3.2.0"
  },
  "scripts": {
     "electron": "electron .",
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject"
  },
  "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"
    ]
  },
  "devDependencies": {
    "electron-builder": "^21.2.0"
  }
}
```

You can read all of the available options [here](https://www.electron.build/configuration/configuration).

Note: For projects created using _create-react-app_, you _must_ put the value to the `"homepage"` property to `"./"` so that the paths correctly resolve throughout the app, since CRA [automatically produces our files assuming our app is hosted from the server root](https://create-react-app.dev/docs/deployment#step-1-add-homepage-to-packagejson). This is to ensure that the generated `index.html` file correctly loads the assets after building. If you're unsure of what this means, just trust me and do it :).

When you run `npm run electron` it will give an error like this:

![electron-start-error.jpg](https://jsmanifest.s3-us-west-1.amazonaws.com/posts/create-your-first-react-desktop-application-in-electron-with-hot-reload/electron-start-error.jpg)

That's because _electron_ cannot find a file to read from. We can create an `electron.js` file in the root directory or we can create a start script in the `src` directory which is more intuitive. Let's create a `start.js` file in the _src_ directory and write some code to initialize a BrowserWindow with some fixed dimensions:

```js
const electron = require('electron')
const app = electron.app
const BrowserWindow = electron.BrowserWindow

const path = require('path')

let mainWindow

function createWindow() {
  mainWindow = new BrowserWindow({ width: 800, height: 600 })

  mainWindow.loadURL(`file://${path.join(__dirname, '../public/index.html')}`)

  mainWindow.on('closed', () => {
    mainWindow = null
  })
}

app.on('ready', createWindow)

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

app.on('activate', () => {
  if (mainWindow === null) {
    createWindow()
  }
})
```

After we've done that we have to add a new property to `package.json` pointing towards this file so that running `npm run electron` will guide the program to load up `start.js`:

```json
{
  "name": "electron-react-typescript-app",
  "version": "0.1.0",
  "private": true,
  "main": "src/start.js",
  "build": {
    "appId": "some.id.ofyours",
    "directories": {
      "buildResources": "assets"
    },
```

Running `npm run electron` will now open up a window loading up the `loading.html` file:

![npm run electron](https://jsmanifest.s3-us-west-1.amazonaws.com/posts/create-your-first-react-desktop-application-in-electron-with-hot-reload/npm+run+electron.jpg)

Great! Things are starting to look good now :)

Now let's go ahead and run `npm start`. The script will now load up a web page of our react code successfully!

![npm start](https://jsmanifest.s3-us-west-1.amazonaws.com/posts/create-your-first-react-desktop-application-in-electron-with-hot-reload/npm+start.jpg)

But wait a minute... this is _not_ what we are looking for. We're supposed to be seeing our react code _in the electron window_, so why are we seeing it in the browser instead?

...that's because we essentially have two different processes going on that are doing different things, that have _no_ idea that the other process exists!

So what we're going to have to do is to make electron point to the web server because it has an API that can load up web pages by URL (read about the API method [here](https://electronjs.org/docs/api/browser-window#winloadurlurl-options)). This means that we won't be using the browser anymore since we are building a desktop application and that electron can load up content into each of its windows by giving them URLs. So we can instead use the electron windows (which will have access to node.js modules and the local file system).

To be able to make this happen, we will be installing a useful package to detect if the app is run in _development_ or _production_ mode. If the app is runnning in dev mode, then we will use the web server. If the app isn't then that means we have built the files using `electron-builder` where we load up the app contents through some executable. That's what electron-builder was for.

Go ahead and install `electron-is-dev`:

```shell
npm i electron-is-dev
```

Now we are going to go ahead and require this in our electron script and use it like so:

```js
const electron = require('electron')
const app = electron.app
const path = require('path')
const isDev = require('electron-is-dev')
const BrowserWindow = electron.BrowserWindow

let mainWindow

function createWindow() {
  mainWindow = new BrowserWindow({
    width: 800,
    height: 600,
    webPreferences: {
      nodeIntegration: true,
    },
  })

  mainWindow.loadURL(
    isDev
      ? 'http://localhost:3000'
      : `file://${path.join(__dirname, '../build/index.html')}`,
  )

  mainWindow.on('closed', () => {
    mainWindow = null
  })
}

app.on('ready', createWindow)

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

app.on('activate', () => {
  if (mainWindow === null) {
    createWindow()
  }
})
```

The important lines to look at are these:

```js
mainWindow.loadURL(
  isDev
    ? 'http://localhost:3000'
    : `file://${path.join(__dirname, '../build/index.html')}`,
)
```

Now instead of directly loading up the `index.html` file, we applied a condition to use the webserver available from CRA in dev mode or proceed to load up the `index.html` file (when the environment is _not_ development).

You might have also noticed that we no longer used the path `../public/indx.html` and instead changed it to `../build/index.html`. This is because CRA internally does _not_ process files inside the `public` folder, [but instead moves them over _untouched_ to the `build` folder](https://create-react-app.dev/docs/using-the-public-folder#adding-assets-outside-of-the-module-system). Since the `build` folder will end up having all the generated files in the end, we had to point to it.

Now go ahead and run `npm start`, then run `npm run electron`.

You should now see this:

![electron with create-react-app](https://jsmanifest.s3-us-west-1.amazonaws.com/posts/create-your-first-react-desktop-application-in-electron-with-hot-reload/electron-with-cra.jpg)

Hurray!

We get the benefits of hot reloading from CRA right into the electron window along with node.js modules and the local file system environment right into the "_web_" page. How neat is this?

Oh yeah, if you're seeing a browser tab being opened, try setting `BROWSER=none` to your `package.json` script like this:

```json
"start": "cross-env BROWSER=none npm run react-start",
```

Lets now make the main electron process restart when we make changes to `start.js`, because currently we only have hot-reloading enabled for the web page UI.

For this, install `electron-reload` and `nodemon`:

```shell
npm i -D electron-reload nodemon
```

Change your `npm run electron` script to this:

```json
"electron": "cross-env NODE_ENV=dev nodemon --exec \"\"electron .\"\"",
```

And simply just require the `electron-reload` package in `start.js`:

```js
const electron = require('electron')
const app = electron.app
const path = require('path')
const isDev = require('electron-is-dev')
require('electron-reload')
const BrowserWindow = electron.BrowserWindow
```

And now the electron window should restart itself when you make changes to `start.js`.

## Conclusion

Congrats, you now know how to create a react desktop application in JavaScript using electron! I hope you found this to be valuable and look out for more in the future!

Find me on [medium](https://medium.com/@jsmanifest)
Join my [newsletter](https://app.getresponse.com/site2/javascript-newsletter?u=zpBtw&webforms_id=SM2hz)