How to use React Router with Electron? - javascript

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?

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:
https://github.com/ReactTraining/react-router/blob/master/packages/react-router-dom/docs/api/HashRouter.md

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?

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>
);

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

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 "/"

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).

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;
}

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 #.

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)}`;
}

Related

React Router V6 - NotFound component not working with dynamic parameter/slug?

I have installed "react-router-dom": "^6.0.0-beta.0" and created some page routes. Facing problems where the Page404 / NotFound component is not working if a page doesn't exist. For some reason when I am using dynamic page/post slug the component NotFound will not work if there is no page/post with this ID.
Is there an option inside React Router which solves this issue?
import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
import AllPosts from "components/AllPosts";
import SinglePost from "components/SinglePost";
import NotFound from "components/Page404";
const App = () => (
<Router>
<Routes>
<Route element={<AllPosts />} path="/" exact />
<Route element={<SinglePost />} path="/:slug" />
<Route element={<NotFound />} path="*" />
</Routes>
</Router>
);
export default App;
The Routes and Route component can't possibly know ahead of time that some dynamic path, "/someUnsupportedIdValue" for example, isn't a valid path to be handled by SinglePost.
The options you have here are:
Conditionally render some alternative UI in SinglePost, something along the lines of "A page by this ID isn't found".
Check if a page with matching ID exists, conditionally render that page, or a redirect (Navigate component replaced Redirect in v6) to your more generic 404 page (actually, a redirect to any "throw away" path that isn't explicitly handled already will land you on your 404 page). Or you can imperatively redirect with a navigate(to, { replace: true }).
Try to remove exact because was removed from v6 and make sure Page404 is the correct component or create Notfound.jsx
Check if the post not exists then redirect to Notfound page.

react-router switch not working as expected

I'm learning react and got stuck at react-routes
consider the following:
import React from "react";
import { BrowserRouter, Route, Switch } from "react-router-dom";
import HelloWorld from "./HelloWorld.jsx";
const Root = () => {
return (
<BrowserRouter>
<div>
<Switch>
<Route path="/" exact component={HelloWorld} />
<Route component={NoMatch} />
</Switch>
</div>
</BrowserRouter>
);
};
function NoMatch({ location }) {
return (
<div>
<h3>
No match found
</h3>
</div>
);
}
export default Root;
on '/' this route, it renders HelloWorld component as expected but on other routes for examples abc it displays Cannot GET /abc instead of No match found
This happens because your server is not set up to handle those routes specifically. The server it what determines what exists and what doesn't - react-router is just a tool to exploit it a bit.
Fix 1
You can avoid this issue by importing HashRouter from the react-router-dom package, rather than BrowserRouter.
The result will be routing based on URLs with a #/ prepending the route itself. So your route of / will now actually be #/.
Fix 2
Set up a reverse proxy (nginx) as the intermediary to serve your page from the Node.js server. Use a broad wildcard or get specific if you know you need more specific configurations. It will pass along the request URI/path to node still but not try to use them as separate endpoints each time or read files from the file system.
First you check your react version then after do like this if v5.1 and above
In order to use v6, you'll need to convert all your elements to <Routes>
You want to replace component to element into <Route ...>
example:- App.js
import Home from "./Home";
import About from "./About";
import {BrowserRouter as Router, Route, Routes } from "react-router-dom";
function App() {
return (
<Router>
<Routes>
<Route exact path="/" element={<Home />} />
<Route path="/about" element={<About />} />
</Routes>
</Router>
);
}
export default App;
after that your switch related error maybe gone!
if still not working comment me i will help you
The code works just fine if you used create-react-app for setting up the react project but if you're using webpack configurations for manually setting up the project it requires devServer: {historyApiFallback: true,} for react routes to work.
Have a look into the sandbox it will help you:
https://codesandbox.io/s/py114mqzj0
Please upgrade your dependencies as follows:
"dependencies": {
"react": "16.5.2",
"react-dom": "16.5.2",
"react-router-dom": "4.3.1",
},
This seems to be working fine for me with latest React and react-router-dom.
import { BrowserRouter, Route, Switch } from 'react-router-dom';
const HelloWorld = () => {
return <div>Hello World</div>;
};
const Root = () => {
return (
<BrowserRouter>
<div>
<Switch>
<Route path='/' exact component={HelloWorld} />
<Route component={NoMatch} />
</Switch>
</div>
</BrowserRouter>
);
};
function NoMatch() {
return (
<div>
<h3>No match found</h3>
</div>
);
}
export default Root;
Code sandbox link to try it out

How to handle front-end and back-end paths on browser refresh

I'm using React, React-router and Play Framework to serve and render an application.
This is what my Routes.jsx looks like:
const Routes = (
<Route path="/">
<Route component={App}>
<Route path="attributes/:itemId" component={AttributesContainer}/>
<IndexRoute component={ItemsContainer}/>
</Route>
</Route>
);
export default Routes
As you can see, path /attributes/:itemId renders a component called AttributesContainer.
This path is called from ItemsContainer in this function:
_onClickAttributes(itemId) {
browserHistory.push({pathname: '/attributes/' + itemId});
}
Everything works just fine there. But if I refresh the browser while in /attributes/:itemId, back-end returns
Action Not Found
For request 'GET /attributes/5'
instead of the front end component. I understand the browser is doing a get request and that's why I get this message.
So, what's the best technique to handle this?

react-router-redux redirect to absolute url

I'm migrating a react application and I'm trying to split it. Basically, I would like to redirect some client-side react routes to absolute urls (or relative, but at least go with a server roundtrip, where reverse proxying is done)
Note that
react-router 3.0.0
react-router-redux 4.0.7
The app have these urls
http://myhost/ => homepage
http://myhost/someroute1 => a first route
http://myhost/someroute2 => a second route
http://myhost/someroute3 => a third route
Everything is inside react right now.
Routing looks like this :
<Provider store={store}>
<Router history={history}>
<Route path="/" component={Root}>
<IndexRoute component={Home} />
<Route path="/someroute1" component={Route1} />
<Route path="/someroute2" component={Route2} />
<Route path="/someroute3" component={Route3} />
</Route>
</Router>
</Provider>
The goal is to redirect, say, routes "/" and "/someroute2" to static urls (server urls). As so :
http://myhost/ => http://anotherhost/
http://myhost/someroute1 => keep react routing
http://myhost/someroute2 => http://anotherhost/anotherroute5
http://myhost/someroute3 => keep react routing
The question is simple : is is possible to replace, in a clean way, a react router route with an absolute url ?
I heard about Redirect and IndexRedirect components, but I can't figure how to use it properly, and, due to a lack of react / react-router, I can't figure if there would be any dangerous side-effects (in history for example).
Use Route's render prop instead of component. That way, you can specify a function to be called instead of a component to be instantiated. In the function, perform the navigation the old-fashioned way, using window.location.href:
<Route
path="/someroute2"
render={() => {
window.location.href = "http://anotherhost/anotherroute5";
return null;
}}
/>
Partially based on #brub answer, I've found a solution using a dumb component.
import React, { Component } from 'react'
export default class MyRedirectRoute extends Component {
componentDidMount() {
window.location.href = //my url here
}
render() {
return null
}
}
That I pass like this
<Route path="/someroute3" component={MyRedirectRoute} />
Though, I'm still not aware of a few things :
Is this a recommended solution ?
Are there any history side-effect ?
Is there any better (more react-router) solution than window.location.href ? I tried this.context.history without any success...
I'm waiting for feedback / better solution before accepting my own answer
You probably don't need React Router for this. The creator of React Router suggests using the <a> tag.
I haven't tried it but syntactically you could do it like this:
<Route
path="/someroute2"
render={() => <Redirect to="http://anotherhost/anotherroute5" />}
/>

Symfony and React router, no route found

I am using Symfony3 and I am creating a bundle using React.js using itself react-router.
The issue is when I use the routing in react, if I reload the page the symfony routing module send 'No Route Found"
My routes are /admin for the index page and /admin/data for the next page.
When I load the page /admin everything is good, I click on the link to go to /admin/data, everything works, react send me dynamically on it, but now when I refresh (F5) the page /admin/data, Symfony intercept it and try to find the routing in its code and redirect to /404 "No Route Found".
I know on AngularJs, the framework is using ancors Path "localhost://admin/#/data", which seems easier to manage but react-router use "localhost://admin/data"
My symfony routing:
admin:
path: /admin
defaults: { _controller: BiBundle:Admin:default }
My React routing:
import { Router, browserHistory } from "react-router";
<Router history={browserHistory}>
<Route path="/admin" component={App}>
<IndexRoute components={{content: DashboardPage}} />
<Route path="data/list"
components={{content: DataListPage}} />
<Route path="*"
components={{content: _ => <h1>Page not found.</h1>}} />
</Route>
</Router>
My link on /admin page:
<Link to={'/admin/data/list'}>Data</Link>
I was thinking to modify my .htaccess to redirect all /admin/* to /admin but seems overkill for that issue.
I am using Apache2 server too.
EDIT
I have replaced browserHistory by hashHistory
import { Router, hashHistory } from "react-router";
<Router history={hashHistory}>
<Route path="/" component={App}>
<IndexRoute components={{content: DashboardPage}} />
<Route path="data/list"
components={{content: DataListPage}} />
<Route path="*"
components={{content: _ => <h1>Page not found.</h1>}} />
</Route>
</Router>
It had the result to change my path as it is used in AngularJs (or close enough), so now I have /admin#/ and /admin#/data/list, so symfony always catch /admin and react-router catch #/ or #/admin/data
What do you think ? Is it the good methodology ?
To make your AJAX routing accessible from url directly (i.e. typing /admin/data in browser URL) without being redirected server-side, you need to make your base route (the Symfony route that renders your React application) taking a parameter that will represents your AJAX routing.
To do this, the parameter must allow slashes, example:
admin:
path: /admin/{reactRouting}
defaults: { _controller: BiBundle:Admin:default, reactRouting: null }
requirements:
reactRouting: ".+"
For a more complex routing, you should look at the FOSJsRoutingBundle that could help you.

Categories

Resources