How to use React Router with Electron?
Asked Answered
A

11

81

Using this boilerplate as reference I created an Electron app. It uses webpack to bundle the scripts and express server to host it.

Webpack config is practically same as this and server this.

Electron's script loads:

mainWindow.loadURL('file://' + __dirname + '/app/index.html');

And index.html loads the script hosted by the server:

<script src="http://localhost:3000/dist/bundle.js"></script>

I run electron index.js to build the app and node server to start server which using webpack bundles the scripts.

It works fine, my React component App is mounted. But how I integrate react-router into this?

I implemented it the same way I would in a browser app. I get this error:

[react-router] Location "/Users/arjun/Documents/Github/electron-app/app/index.html" did not match any routes

It is taking file path as the route. Going through the boiler plate code did not help. What am I missing?

Allfired answered 8/4, 2016 at 17:7 Comment(2)
Getting the exact same thing. Did you find a solution, @arjun-u – or did you just go for hashHistory instead?Disenfranchise
I used hashHistory.Allfired
M
33

Another option would be to use hashHistory instead. Actually, in your referenced repo you can see that they're using hashHistory, how about trying that and posting back?

Marchand answered 6/5, 2016 at 20:27 Comment(2)
HashRouter is deprecated, so I went with Niekert's answer. github.com/ReactTraining/react-router/blob/master/packages/…Agrology
HashRouter is not deprecated in 2019.Kook
M
171

Had to Replace BrowserRouter with HashRouter.

import {
  HashRouter,
  Route
} from "react-router-dom";

And then in my index.js or the entry file of the Electron app I had something like this:

<HashRouter>
  <div>
    <Route path="/" exact     component={ Home } />
    <Route path="/firstPage"  component={ FirstPage } />
    <Route path="/secondPage" component={ SecondPage } />
  </div>
</HashRouter>

And then everything just worked.

The reasoning: BrowserRouter is meant for request-based environments whereas HashRouter is meant for file-based environments.

Read more here:

Mekka answered 18/5, 2018 at 6:7 Comment(11)
Man! you saved my entire week! I tried trillions of combinations and googled it like for ever.. Now, it works like charm! thank youFlorella
@Florella I had the exact same week so when I found out how to do it I thought I better share it save some other poor soul. Glad it saved you some time and sanity! =-)Mekka
In my case this solution didn't helped. I'm still getting a blank page :(Salty
Thank you so much it's working like a charm❤️ you saved my entire project.Goddamn
@JoshuaPinter i created a menu item. now i wanted to navigate to another page of my application. i am using react Hash Router. Not sure where do startBiddle
I tried this answer, but still, get a blank pageByron
@Salty Wasn't working for me either until I explicitly set in my webpack config the devServer.historyApiFallback property in to true. webpack.js.org/configuration/dev-server/…Plenty
For anyone still getting a blank page, you're likely not including a # in mainWindow.loadURL('file://index.html#/home'). Check out my answer for details.Dooley
seems promising but reactrouter.com/en/6.14.2/router-components/hash-router says "We strongly recommend you do not use HashRouter unless you absolutely have to." which makes me bit uneasyMohl
@Mohl This answer was from 5 years ago. What are they saying to use as an alternative now?Mekka
I asked them here: github.com/remix-run/react-router/discussions/10724 it seems like people still use HashRouter although it is deprecated nowMohl
M
33

Another option would be to use hashHistory instead. Actually, in your referenced repo you can see that they're using hashHistory, how about trying that and posting back?

Marchand answered 6/5, 2016 at 20:27 Comment(2)
HashRouter is deprecated, so I went with Niekert's answer. github.com/ReactTraining/react-router/blob/master/packages/…Agrology
HashRouter is not deprecated in 2019.Kook
D
27

I'm using React Router v4 and didn't want to fallback to the HashRouter, so I solved it with something amongst the likes of:

import { Redirect, BrowserRouter } from 'react-router-dom';

const App = () => (
  <BrowserRouter>
    <div>
      {window.location.pathname.includes('index.html') && <Redirect to="/" />}
    </div>
  </BrowserRouter>
);
Dov answered 12/5, 2017 at 13:21 Comment(2)
Your solution works except reloading electron will lead to a blank page. It's not a problem in "production" but in development I often need to reload. Does it happen to you?Graveclothes
Worked in terms of loading the app but all imported assets (images/vids) are not loaded!Poodle
H
22

Best option at the time of this answer is to use the MemoryRouter, worked for me :)

Hurwitz answered 19/9, 2017 at 18:26 Comment(1)
Reloading the page will lose the state thoughFit
C
9

The (current) react-router docs say:

Generally speaking, you should use a <BrowserRouter> if you have a server that responds to requests and a <HashRouter> if you are using a static file server.

An Electron app is basically a static file server.

MemoryRouter can also work, so long as all routing originates from within the React part of the app. It only falls down when you want to navigate to a specific page from the Browser process, e.g. you want to pop up a new window and navigate directly to a "General Preferences" page. In that case, you can do this with HashRouter:

prefsWindow.loadURL(`file://${__dirname}/app/index.html#/general-prefs`);

I don't think there is a way to do that with MemoryRouter (from the Browser process).

Carrie answered 21/12, 2017 at 13:57 Comment(0)
A
8

What about simply using Switch to default to "/" as follows:

<Switch>
  <Route path="/" exact component={Home}/>
  <Route path="/foo" component={Foo}/>
  <Route path="/bar" component={Bar}/>
  <Route render={() => <Redirect to="/"/>}/>
</Switch>

This way, "/index.html" will redirect to "/"

Accolade answered 8/3, 2020 at 14:9 Comment(2)
Every refresh leads to an empty site and whole app needs to be refreshed... How did you solve this?Hyperactive
@Hyperactive I do have the same problem,Happen
D
5

It all depends on what kind of URL you pass to mainWindow.loadURL.

file://...

If you load index.html directly through the file:// protocol, such as

mainWindow.loadURL('file://' + path.join(__dirname, '../index.html#/home'))

then you need to use HashRouter:

<HashRouter>
  <Routes>
    <Route path='/home' element={<MyHomeScreen/>}>
  </Routes>
</HashRouter>

Note that the # in index.html#/home is very important!

Why?

Think about what would happen if you wrote index.html/home. Your computer would try to retrieve a home file inside an index.html directory. The # prevents this, and thus we need to use HashRouter.

http://localhost:3000

If you load index.html from a server such as localhost:3000, then you have two options:

  • include the #, as in
    mainWindow.loadURL('http://localhost:3000#/home')
    
    and use HashRouter exactly as above,

OR

  • don't include the #, as in
    mainWindow.loadURL('http://localhost:3000/home')
    
    and use BrowserRouter like this:
    <BrowserRouter>
      <Routes>
        <Route path='/home' element={<MyHomeScreen/>}>
      </Routes>
    </BrowserRouter>
    

Many people prefer to use BrowserRouter in this case because it avoids complicating the URL with a #.

Dooley answered 9/6, 2022 at 15:57 Comment(0)
M
3

Agree with Niekert. But I believe it is better to handle like this before any route management.

if ( window.location.pathname.includes('index.html') ) {
    location.pathname = ROUTES.ROOT;
}
Mange answered 4/9, 2018 at 12:32 Comment(0)
S
0

In your main process:

mainWindow.loadURL(resolveHtmlPath('index.html'));

In your renderer process:

import { HashRouter as Router, Routes, Route } from 'react-router-dom';

//...

<Router>
  <Routes>
    <Route path="/" element={<Home />} />
    <Route path="/search" element={<Search />} />
    <Route
      path="/library"
      element={<Library />}
      />
  </Routes>
</Router>

The call to resolveHtmlPath removed the need for using hashtags for me. Using a BrowserRouter out of the box gave me the warning message in dev tools.

This function is in the Electron React Boilerplate project that you referenced:

import { URL } from 'url';
import path from 'path';

export function resolveHtmlPath(htmlFileName: string) {
  if (process.env.NODE_ENV === 'development') {
    const port = process.env.PORT || 1212;
    const url = new URL(`http://localhost:${port}`);
    url.pathname = htmlFileName;
    return url.href;
  }
  return `file://${path.resolve(__dirname, '../renderer/', htmlFileName)}`;
}
Swedenborgianism answered 9/2, 2023 at 4:39 Comment(0)
B
0

Using react-router-dom version 6, I just used HashRouter instead of BrowserRouter and it worked perfect.

  • Version
  "dependencies": {
    "react": "^18.2.0",
    "react-dom": "^18.2.0",
    "react-scripts": "5.0.1",
    "react-router-dom": "^6.22.3",
    "web-vitals": "^2.1.4"
  },
  "devDependencies": {
    "concurrently": "^7.2.2",
    "electron": "^29.2.0",
    "electron-packager": "^17.1.2",
    "wait-on": "^6.0.1"
  },
  • Scripts
"electron-dev": "concurrently \"BROWSER=none npm start\" \"wait-on http://localhost:3000 && electron .\"",
"package-win": "npm run build  && electron-packager . app-name --overwrite --platform=win32 --arch=x64 --icon=assets/icons/win/icon.ico --asar --prune=true --out=release-builds"

App.js


    import { HashRouter as Router, Routes, Route } from 'react-router-dom';
    
    import PageOne from './pages/PageOne';
    import PageTwo from './pages/PageTwo';
    import Layout from './components/Layout';
    
    const App = () => {
    
      return (
        <Router>
          <Routes>
            <Route path='/' exact element={<Layout />}>
              <Route path='/page-one' exact element={<PageOne />} />
              <Route path='/page-two' exact element={<PageTwo />} />
            </Route>
          </Routes>
        </Router>
      );
    };
    
    export default App;

Layout.js


    import { Outlet } from 'react-router-dom';
    
    const Layout = () => {
      return (
        <main className='root'>
          <div className='root-container'>
            <div className='wrapper'>
              <Outlet />
            </div>
          </div>
        </main>
      );
    };
    
    export default Layout;

Bipod answered 13/4 at 15:37 Comment(0)
B
0

Using react-router-dom version 6, I just used HashRouter instead of BrowserRouter and it worked perfectly.

  • Version
"dependencies": {
    "react": "^18.2.0",
    "react-dom": "^18.2.0",
    "react-scripts": "5.0.1",
    "react-router-dom": "^6.22.3",
    "web-vitals": "^2.1.4"
  },
  "devDependencies": {
    "concurrently": "^7.2.2",
    "electron": "^29.2.0",
    "electron-packager": "^17.1.2",
    "wait-on": "^6.0.1"
  },
  • Scripts
"electron-dev": "concurrently \"BROWSER=none npm start\" \"wait-on http://localhost:3000 && electron .\"",
"package-win": "npm run build  && electron-packager . app-name --overwrite --platform=win32 --arch=x64 --icon=assets/icons/win/icon.ico --asar --prune=true --out=release-builds"

App.js


    import { HashRouter as Router, Routes, Route } from 'react-router-dom';
    
    import PageOne from './pages/PageOne';
    import PageTwo from './pages/PageTwo';
    import Layout from './components/Layout';
    
    const App = () => {
    
      return (
        <Router>
          <Routes>
            <Route path='/' exact element={<Layout />}>
              <Route path='/page-one' exact element={<PageOne />} />
              <Route path='/page-two' exact element={<PageTwo />} />
            </Route>
          </Routes>
        </Router>
      );
    };
    
    export default App;

Layout.js


    import { Outlet } from 'react-router-dom';
    
    const Layout = () => {
      return (
        <main className='root'>
          <div className='root-container'>
            <div className='wrapper'>
              <Outlet />
            </div>
          </div>
        </main>
      );
    };
    
    export default Layout;

Bipod answered 13/4 at 17:11 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.