Electron with React and React-Router-DOM

Is it possible to make an electron to only be instantiated once?

Yes, in Electron.js, it's very common to want the application to only have one instance running at a time (e.g. to avoid opening multiple windows if the user tries to launch the app again). Electron provides a built-in way to do this using the app.requestSingleInstanceLock() API.

βœ… Example: Ensuring a Single Electron App Instance

javascript
const { app, BrowserWindow } = require('electron'); let mainWindow; // Try to get the single instance lock const gotTheLock = app.requestSingleInstanceLock(); if (!gotTheLock) { // Another instance is already running β€” quit this one app.quit(); } else { app.on('second-instance', (event, commandLine, workingDirectory) => { // Someone tried to run a second instance β€” focus the main window if (mainWindow) { if (mainWindow.isMinimized()) mainWindow.restore(); mainWindow.focus(); } }); // Create the main window app.whenReady().then(() => { mainWindow = new BrowserWindow({ width: 800, height: 600, }); mainWindow.loadURL('https://example.com'); mainWindow.on('closed', () => { mainWindow = null; }); }); app.on('window-all-closed', () => { if (process.platform !== 'darwin') app.quit(); }); }

πŸ” Explanation:

  • app.requestSingleInstanceLock() returns true if the current instance is the first one. If it's false, another instance is already running.

  • The second-instance event is emitted in the first instance when a second instance is started. This is where you can focus or restore the main window.

  • The second instance simply exits with app.quit().

🧠 Note:

  • This only works in packaged apps or when running from the same app path.

  • On macOS, apps behave slightly differently β€” often keeping the app running even when no windows are open. Handle that with app.on('activate', ...).

Let me know if you're using a specific Electron version or need this to work with specific behaviors like protocol handling or deep linking.

how to use react in electron?

To use React in an Electron app, you can combine the two by:

  • Using Electron for the backend (main process and window management).

  • Using React for the frontend (rendered in a BrowserWindow via a web page).


πŸš€ 1. Setup: Create an Electron + React App (Quickest Way)

You can set this up manually or use tools like Vite, Webpack, or Create React App. Below is a basic setup using Vite + Electron, which is modern and fast.


βœ… Option 1: Vite + React + Electron (Recommended Modern Stack)

πŸ“ Folder Structure

my-electron-app/ β”œβ”€ main/ ← Electron Main process code β”‚ └─ main.js β”œβ”€ renderer/ ← React code (Vite project) β”‚ β”œβ”€ index.html β”‚ β”œβ”€ main.jsx β”‚ └─ ... β”œβ”€ package.json

πŸ”§ Step-by-step Setup

Step 1: Initialize project

bash
mkdir my-electron-app cd my-electron-app npm init -y

Step 2: Add dependencies

bash
npm install electron --save-dev npm create vite@latest renderer -- --template react cd renderer npm install cd ..

Step 3: Create main.js (Electron entry point)

Create a main/main.js:

js
const { app, BrowserWindow } = require('electron'); const path = require('path'); function createWindow() { const win = new BrowserWindow({ width: 800, height: 600, webPreferences: { preload: path.join(__dirname, 'preload.js'), // optional }, }); win.loadURL('http://localhost:5173'); // Vite dev server } app.whenReady().then(() => { createWindow(); });

Step 4: Add start script in package.json

json
"scripts": { "dev": "concurrently \"npm:dev:*\"", "dev:react": "cd renderer && vite", "dev:electron": "electron main/main.js" }, "devDependencies": { "concurrently": "^8.0.0" }

Install concurrently:

bash
npm install concurrently --save-dev

πŸ§ͺ Run Your App

bash
npm run dev

You should see a window open with your React app inside.


πŸ“¦ Packaging the App (React + Electron)

To build the app for production:

  • Build React:

bash
cd renderer npm run build cd ..
  • Update main.js to load the built HTML:

js
win.loadFile(path.join(__dirname, '../renderer/dist/index.html'));

🧠 Summary

Component Role
Electron Manages windows and system APIs
React Builds the UI (in BrowserWindow)
Vite Fast frontend dev/build tool

Let me know if you'd prefer using Create React App, Webpack, or want a fully packaged Electron + React template, and I can generate that too.

how to handle routes in my app then?

To handle routes in your Electron + React app, you use React Router just like in any other React web app. However, since Electron apps are typically single-page applications (SPA) running locally, there are a few extra considerations to ensure routing works smoothly in development and production.


βœ… Step-by-Step: Handling Routes in Electron + React

1. πŸ“¦ Install React Router

In your React frontend (e.g., renderer/):

bash
npm install react-router-dom

2. πŸ” Set Up Routing in React

In your renderer/src/main.jsx (or App.jsx):

jsx
import React from 'react'; import ReactDOM from 'react-dom/client'; import { BrowserRouter as Router, Routes, Route, } from 'react-router-dom'; import Home from './pages/Home'; import About from './pages/About'; const App = () => ( <Router> <Routes> <Route path="/" element={<Home />} /> <Route path="/about" element={<About />} /> </Routes> </Router> ); ReactDOM.createRoot(document.getElementById('root')).render(<App />);

3. πŸ§ͺ During Development (Vite + Electron)

Everything should work as expected β€” routing is handled by React Router using the browser's history API.

When running vite (npm run dev), Electron loads from:

js
win.loadURL('http://localhost:5173');

React Router works like a regular web app.


4. ⚠️ In Production (Load from file://)

When Electron loads your built app from file://, BrowserRouter can fail to handle deep links like /about. This is because there's no real server to respond with your index.html.

βœ… Solution: Use HashRouter instead

In App.jsx or main.jsx, replace:

jsx
import { BrowserRouter } from 'react-router-dom';

with:

jsx
import { HashRouter } from 'react-router-dom';

And change:

jsx
<BrowserRouter>

to:

jsx
<HashRouter>

βœ… Why use HashRouter?

  • URLs look like app://index.html#/about instead of app://index.html/about.

  • Works out of the box with local file:// URLs.

  • Avoids 404 errors on refresh or direct navigation.


5. βœ… Final Tip: Dynamic Routing

You can still use dynamic routes:

jsx
<Route path="/user/:id" element={<UserPage />} />

And get the id with useParams() in your component:

jsx
import { useParams } from 'react-router-dom'; const { id } = useParams();

Summary

Mode Router to Use Notes
Development BrowserRouter Works fine with Vite + dev server
Production HashRouter Safe with Electron loading file:// HTML

Let me know if you want code examples for navigation menus or linking between routes!

How to have both use electron to load some pages and use react-router-dom for some others?

Great question β€” you're looking to build a hybrid routing setup in an Electron + React app, where:

  • Some pages are loaded directly by Electron (via BrowserWindow.loadURL() or loadFile()), and

  • Other pages are handled within React using react-router-dom.

This can work well if you want separate windows or need native logic separation (e.g., login screen in one window, dashboard in another).


βœ… Solution Outline

You can combine Electron routing (main process) with React Router (renderer process):

🧠 Core Idea:

  • Electron decides which window to show (e.g. login, dashboard).

  • React Router controls navigation within that window.


πŸ› οΈ Step-by-Step Setup

πŸ”Ή 1. Use BrowserWindow.loadURL() to Route from Electron

js
// main/main.js const { app, BrowserWindow } = require('electron'); const path = require('path'); function createLoginWindow() { const win = new BrowserWindow({ width: 500, height: 400 }); win.loadURL('http://localhost:5173/#/login'); // use hash route } function createDashboardWindow() { const win = new BrowserWindow({ width: 1000, height: 700 }); win.loadURL('http://localhost:5173/#/dashboard'); } app.whenReady().then(() => { // Example: open login screen first createLoginWindow(); });

πŸ’‘ In production, use win.loadFile('dist/index.html', { hash: '#/login' }).


πŸ”Ή 2. Use HashRouter in React (so deep links work with file://)

jsx
import { HashRouter, Routes, Route } from 'react-router-dom'; import Login from './pages/Login'; import Dashboard from './pages/Dashboard'; function App() { return ( <HashRouter> <Routes> <Route path="/login" element={<Login />} /> <Route path="/dashboard/*" element={<Dashboard />} /> </Routes> </HashRouter> ); }

πŸ”Ή 3. In React, Route Internally as Usual

You can navigate between /dashboard/home, /dashboard/settings, etc. β€” React Router handles that normally inside the dashboard window.

jsx
<Route path="/dashboard/home" element={<Home />} /> <Route path="/dashboard/settings" element={<Settings />} />

🎯 Example Use Cases

Electron Window Route You Load React Pages Inside
Login #/login Only login screen
Main App #/dashboard Dashboard, Settings, etc
Popup #/popup/settings Small settings view

⚠️ Notes

  • Use HashRouter to avoid issues with file:// and deep linking.

  • Avoid switching windows within React Router β€” Electron should create/destroy windows.

  • You can send data between windows via ipcRenderer / ipcMain if needed.


Bonus: Dynamic Window Creation from React

You can send a message from React to Electron to open a new window:

js
// In renderer window.electronAPI.openDashboard()
js
// In preload.js contextBridge.exposeInMainWorld('electronAPI', { openDashboard: () => ipcRenderer.send('open-dashboard') }); // In main.js ipcMain.on('open-dashboard', () => { createDashboardWindow(); });

Let me know if you want a complete working starter with Electron, Vite, React, and hybrid routing β€” I can generate the template for you.

what is the hash route?

Great question! A hash route is a way to implement routing in web applications that works without server configuration, which is why it's commonly used in Electron apps and static deployments.


πŸ”‘ What is a Hash Route?

A hash route uses the # symbol in the URL to simulate navigation.

πŸ“Œ Example:

http://localhost:5173/#/dashboard

Here:

  • The browser loads index.html from localhost:5173

  • The part after # (i.e., #/dashboard) is not sent to the server

  • React Router (or your frontend router) reads the hash and decides what to render


πŸ”„ How It Works

Browsers treat anything after a # in a URL as a fragment identifier, which is:

  • Not sent to the server

  • Handled entirely on the client side (JavaScript)

This is ideal in Electron and static site hosting, where:

  • There’s no backend server to respond with different pages

  • Everything is served from one index.html


βš™οΈ HashRouter vs BrowserRouter (React Router)

Router URL Format Works with file:// or static servers Requires server config
HashRouter http://localhost/#/about βœ… Yes ❌ No
BrowserRouter http://localhost/about ❌ No (breaks with file://) βœ… Yes (needs server)

πŸ“¦ Why Hash Routes Are Used in Electron

Electron loads files using:

js
win.loadFile('dist/index.html');

This uses the file:// protocol, which:

  • Doesn’t support clean URLs like /about

  • Will break if you use BrowserRouter and refresh

βœ… HashRouter avoids this because:

  • It never touches the actual file path

  • Routing happens completely inside your React app


πŸ” Summary

  • Hash route: a route using #, like #/settings

  • Handled completely in the browser

  • Works perfectly in Electron, static sites, and file:// contexts

  • Use HashRouter from react-router-dom to make it work


Let me know if you'd like to see a working code example using HashRouter vs BrowserRouter.