๐โA react-router-dom adapter for Electron apps
If you've already tried using react-router-dom with Electron, had difficulties getting it to work both in development and in production and in different windows, this library is for you!
- ๐ Ready for Development and Production environments
- ๐ฅ Works on Multiple windows
- ๐ฆ Isolated routes by window id
In your terminal, run:
yarn add electron-router-dom
# OR
npm i electron-router-dom
Router DOM is a peer dependency, if you haven't installed it yet or your package manager won't handle it automatically for you, so run:
yarn add react-router-dom
# OR
npm i react-router-dom
The main thing to keep in mind is: you must use the same window id in the Electron Main Process used in createFileRoute
and createURLRoute
functions and in the Electron Renderer Process in the <Router>
component prop names.
import {
app,
BrowserWindow,
BrowserWindowConstructorOptions as WindowOptions,
} from 'electron'
import { createFileRoute, createURLRoute } from 'electron-router-dom'
import { join } from 'path'
function createWindow(id: string, options: WindowOptions = {}) {
const window = new BrowserWindow({
width: 700,
height: 473,
...options,
})
// Don't forget to check if the port is the same as your dev server
const devServerURL = createURLRoute('http://localhost:3000', id)
const fileRoute = createFileRoute(
join(__dirname, '../renderer/index.html'),
id
)
process.env.NODE_ENV === 'development'
? window.loadURL(devServerURL)
: window.loadFile(...fileRoute)
return window
}
app.whenReady().then(() => {
createWindow('main', {
webPreferences: {
preload: join(__dirname, '../preload/index.js'),
},
})
createWindow('about', {
width: 450,
height: 350,
show: false,
})
})
Create a routes.tsx
file:
import { Router, Route } from 'electron-router-dom'
import { MainScreen, AboutScreen, SearchScreen } from './screens'
export function AppRoutes() {
return (
<Router
main={
<>
<Route path="/" element={<MainScreen />} />
<Route path="/search" element={<SearchScreen />} />
</>
}
about={<Route path="/" element={<AboutScreen />} />}
/>
)
}
Then, import the AppRoutes
in your index.tsx
:
import ReactDom from 'react-dom/client'
import React from 'react'
import { AppRoutes } from './routes'
ReactDom
.createRoot(document.querySelector('app') as HTMLElement)
.render(
<React.StrictMode>
<AppRoutes />
</React.StrictMode>
)
A simple example of a MainScreen component:
import { useNavigate } from 'react-router-dom'
// The "App" comes from the context bridge in preload/index.ts
const { App } = window
export function MainScreen() {
const navigate = useNavigate()
return (
<main>
<button onClick={() => navigate('/search')}>Go to Search screen</button>
<button onClick={App.OpenAboutWindow}>Open About window</button>
</main>
)
}
Creates the route for Electron Window loadFile method for production mode with the given window ID.
Params:
path: string
id: string
options?: Electron.LoadFileOptions
Return:
Array: [string, Electron.LoadFileOptions]
Example:
mainWindow.loadFile(
...createFileRoute(
join(__dirname, '../renderer/index.html'),
'main'
)
)
Creates the URL route for Electron Window loadURL method for development mode for the given window ID.
Params:
route: string
id: string
Return: String
Example:
// Don't forget to check if the port is the same as your dev server
mainWindow.loadURL(
createURLRoute(
'http://localhost:3000',
'main'
)
)
The prop names should be the window ids used in main process passsing a Route component to be rendered when route/window matches
Props: [windowID: string]: JSX.Element
Example:
<Router
main={<Route path="/" element={<MainScreen />} />}
about={<Route path="/" element={<AboutScreen />} />}
settings={<Route path="/" element={<SettingsScreen />} />}
/>
<Router
main={
<>
<Route path="/" element={<MainScreen />} />
<Route path="/search" element={<SearchScreen />} />
</>
}
/>
It's the react-router-dom <Route />
component, same props, same usage. ๐
The recommended way to go to handle window states between main and renderer process (like creating or closing a new window) is using IPC. You can check the electron-app boilerplate to see how it was achieved, like:
- A preload script requesting window creation using IPC
- A window creation on main process using IPC
- The Routing doc from electron-app boilerplate
Note: contributions are always welcome, but always ask first, โ please โ before work on a PR.
That said, there's a bunch of ways you can contribute to this project, like by:
- ๐ชฒโReporting a bug
- ๐โImproving this documentation
- ๐จโSharing this project and recommending it to your friends
- ๐ตโSupporting this project on GitHub Sponsors or Patreon
- ๐โGiving a star on this repository