react-router, routes not working - react-router

I am using the react-router and I have the following routes
<Route path = '/' component = {App}>
<Route path = '/login' component = {LoginContainer}>
</Route>
</Route>
Now from the app when I go to 'http://localhost' then it works fine, however when I navigate to http://localhost/login then I get an error saying login cannot be found.

If you're using webpack dev server, you will need to use the History API Fallback option if you want to use browser history.
Basically, when you go to http://localhost/login, the dev server does not have anything at that address to serve you, so you need to tell it to fallback to your index to allow it to load the correct route.

Related

Universal routing with express and react router. Understanding history behaviour

I am using React Router 4.0 and Express 4.14 to create an app that has a mix of single-page-app (SPA) and multi-page-app (MPA). I don't know if that's good practice, but this is not the point. I am actually doing it to learn rather than for a real world app. This idea comes from the scenario where you have strongly separated sections inside an app, as for example a blog and a portfolio.
Client side
So, when I want to navigate as a SPA, I use the Link component from react-router-dom, like <Link to="/reactrouter-route">. If I want to make a request to a route handled by the server, I use <a href="/server-route">.
Server side
I have a middleware logging the path of any request received by my server. I define two routes, each serving a complete SPA. To keep with the blog/portfolio example, imagine I have the following
const express = require('express');
const app = express();
app.use((req, res, next) => {
console.log(req.path);
next();
});
app.get('/', (req, res) => {
res.sendFile('blog.html');
});
app.get('/portfolio', (req, res) => {
res.sendFile('portfolio.html');
});
Behaviour
When I go to / the blog gets loaded as a SPA and I can go to the different posts navigating back to / when I want. Everything works as expected. All this navigation inside the SPA is managed by React Router, and the server only gets the first request to /.
Imagine that from a specific post, say /posts/some-post, I have a link to the portfolio. If I click it, I get a request at the server, and it responds with the portfolio SPA. I can navigate inside the portfolio SPA, but I cannot go back to /posts/some-post. I get the following error:
Cannot GET /posts/some-post
I thought the error was thrown by the server, but surprisingly I don't get any request when going back. I only get requests at the server when going forward through a link (only with <a>).
I kept doing tests and there is no problem if I go back from /portfolio to /. This works as expected.
It gets interesting
I defined a route in my server with just the same rule that I had in my React Router routes. The path I was matching in this new route was /posts/:postid. I set this route to redirect to /. Now, I get the same error if I go from posts/some-post to /portfolio and I try to come back. This is not strange as the server doesn't get a request. It's also normal that I reach / if I go straight to /posts/some-post by typing in the URL in the browser.
But, once I go to /posts/some-post manually, I can go from /portfolio back to /posts/some-post without the error. Now it behaves as if the server was called. In fact, I get a request in the server to fetch the static files. However, I don't get a request to /posts/some-post nor /.
Even then, I would get an error if a go from /posts/some-other-post to /portfolio and try to go back.
Question
I guess this has to do with the cache, but I don't know what is going on there. When is the React Router handling going back? When is the server handlin it? How is the cache involved in this process?
It sounds like you need a clearer mental model of the roles of the server and the client in an SPA. "Single Page" is the important part.
The client, built with React, should never be loading pages from the server. It should be a "single page". In other words, you should not be using <a href="/server-route"> in your client app at all. The client should only get (JSON) data from the server using something like fetch (https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API).
I highly suggest you check out Create React App which also explains how to integrate with a node API backend during development. Basically you want all your client routes to be something like /post/:postid which will be handled by React Router and then that React component would use fetch to get the data from something like /api/posts/10. If you use /api to prefix all your requests to the server it should help your mental model.

How to have react-router in electrode app push in an action file

I have a react app using the walmart electrode framework with uses react router. My question is
a) how can push a route during an action trigger? I tried importing push from react-router but I got a method not found error. I tried instead to use browserHistory.push and that sets the url but for some reason login renders only at /#/login?_k=jazzx rather than at /login.
b) how can I get it to do the /resoure urls rather than the hash #/resource urls. It's a single page app. I realize that it's doing that because its a single page app, but Is there a setting for that? Whats the best practice?
c) what is the querystring that electrode is attaching to things - is that for dev only?
export const tryLogin = (returnUrl = '/') => {
return (dispatch) => {
browserHistory.push('/login'); //this doesn't seem to render the route /#/login_k=somestring does work
return dispatch(createLoginAction({ returnUrl }));
};
}
;
The URL /#/login?_k=jazzx implies that you are using a hash history, but you are attempting to change the URL using browserHistory. You should be passing the browserHistory to your <Router> if you want to use the browser history API (aka have clean URLs).
If you use the browserHistory, you will need to have some sort of code on your server to handle routing set up.

React router, show 404 for non existent resource

I've just started to use react router and the experience turned out to be really nice. I normally do backend but I've joined forces with the front-end team to create our very first fully isomorphic app.
We have a simple router that looks like this
<Route path="/" {...props} component={AppContainer}>
<IndexRedirect to="/home" />
<Route path="/home" component={StaticContainer} require={loadStaticPage} />
<Route path="/contact" component={StaticContainer} require={loadStaticPage} />
<Route path="/:resource" component={ItemsContainer} require={loadItems} />
<Route path="/:resource/:slug" component={ItemContainer} require={loadItem} />
</Route>
All the resources are dynamically fetched from the CMS (we are integrating this with an existing system) and our main issue is how to handle the not found cases.
In the old days I had a express application rendering everything and I did something that looked like this
router.get('/resource/:slug', (req, res, next) => {
const resource = normallyNotSynchronousCall(req.params.slug);
resource ? res.render('something', { resource }) : next();
});
So in case of a non existing resource I just called next() and it would eventually hit the last route that was just a catch all that rendered the 404 page.
I know that we can have the same idea in react router and add this as last route to catch everything.
<Route path="*" component={NotFoundContainer} />
But this will not work if the resources are dynamically fetched from the CMS. Is there a way to get an equivalent of next() in react router to skip the current route and start evaluating the next ones? So I can trigger some action depending on the response from the CMS and tell the router to skip the current route and start evaluating down the chain?
Probably it's an edge case, but I believe there are some uses for it.
Thanks for all the possible replies.

Non-server hosted pages - file:///, chrome://, and about:

Hi all I am new to react router. Things worked great on pages hosted on server. However locally it is failing badly.
I am using react-router to create a browser addon. I have three different cases, all of which are failing. I set the base path to a global constant named BASE_PATH.
First app is url of about:mytool. Plugging this into new URL('about:mytool') correctly shows that host is a blank string. So this is how I set up my router:
I want my path seperator to be ?.
render((
<Router history={browserHistory}>
<Route path="about:mytool?" component={App}>
<IndexRoute component={IndexPage}/>
<Route path="page1" component={Page1}/>
<Route path="page2" component={Page2}/>
<Route path="*" component={NoMatch}/>
</Route>
</Router>
), document.body)
And then my links are like this:
etc.
However doing the above causes absolutely blank to render.
My other two cases are similar. The only difference is that about:mytoolchanges to:
const BASE_PATH = "chrome://mytool/content/app.html"
and last case const BASE_PATH = "file:///C:/Users/mytool.html"
Can you please advise on how to get it to work on non-server hosted pages.
Is it possible to not

Browser History React Router

I am trying to configure my browserHistory. My route is
<Route path="/test" component = {App} />
It works fine if I create a link. But if I put localhost/test in the browser or url I get a 404 error. I assume it cant find it on the server.
Can someone please help me? I am new to react-router. Do I have to configure the server side?
Thank you so much in advance.
Yes, as mentioned in React Router documentation you must configure your server so that it always returns your index page, no matter which path the browser requests.
Using express, supposing you have a /public/index.html file, this would work:
/* Your express includes and init code would go here... */
// Serve static assets normally
app.use(express.static(__dirname + '/public'))
// Handle every other route with index.html, which will contain
// a script tag to your application's JavaScript file(s).
app.get('*', function (request, response){
response.sendFile(path.resolve(__dirname, 'public', 'index.html'))
})
The important thing is app.get('*', ..., which will return the same thing (in this case your index.html file), no matter which path is requested from your browser.
Hope that helps.