ReactJS Router is overriding ExpressJS router - javascript

I have encountered very interesting problem. My React JS root file has multiple routers:
render() {
return (
<div className="container body">
<Route exact path="/" component={<IndexPage/>} />
<Route exact path="/users" component={<Users/>} />
<Route exact path="/users/:userId" component={<User/> }/>
<Route exact path="/routers" component={<Routers/>} />
</div>
);
}
I generate Component depending on route. Then I have build my react components as single javascript file by webpack.
Now I'm trying to show React pages via ExpressJS like this:
const express = require('express');
const app = express();
// This is location where generated Webpack files are located
app.use(express.static(__dirname + '/../build'));
app.get('/api/hello', (req, res) => {
res.json({hello: 'world'})
});
// This is how I render React based pages
app.get(['/', '/users', '/routers'], (req, res) => {
res.sendFile(__dirname + '/../build/index.html');
});
app.listen(9000);
When I open /, /users, /routers paths by my browser, it shows correct React Components.
However, when I try to open /api/hello, It is still trying to generate React Component that does not exist, instead of showing JSON response!
How to stop making React's router overriding Express' router?
=================UPDATE=====================
I have found interesting solution. When I made api/hello request via POSTMAN it has shown to me JSON response.
That means that ReactJS has its own CACHING in browser level which makes it seem like overriding routes.

I have found interesting solution. When I made api/hello request via POSTMAN it has shown to me JSON response.
That means that ReactJS has its own CACHING in browser level which makes it seem like overriding routes.

Related

React Router Dom removes the url sub paths instead of adding the routing page

I have an asp.net MVC application that serves the React app under the following url: http://domain.fake/controller/action. Here is the routing of the react app:
<Routes>
<Route path="/*" element={<Config />}>
<Route path="values" element={<ConfigKeyValues />} />
</Route>
</Routes>
When I try to navigate to the values route using the useNavigate hook:
const nav = useNavigate();
nav("values");
In the URL, instead of adding the /values path, the routing removes the whole controller/action path and only sets the /values path. So instead of getting the url http://domain.fake/controller/action/values, I'm getting http://domain.fake/values, and this is not right.
I'm able to display the component correctly, and I'm not being redirected by the server, but the url is wrong because I can't share that since it does not exist.
How can I prevent the React routing from replacing the base path but adding the new path to the url and displaying the component of that route?
To tell React Router Dom that your application is served from a subfolder, you could use the basename property, which works with createBrowserRouter as well as BrowserRouter:
The basename of the app for situations where you can't deploy to the root of the domain, but a subdirectory.
<BrowserRouter basename="/controller/action">
{/* The rest of your app goes here */}
</BrowserRouter>
createBrowserRouter(routes, {
basename: "/controller/action",
});
Side note, be aware that calling navigate("values"), without / at the beginning of "values" would take into account the path you are on and build on top of it as the doc says.
true navigate method inside react-router-dom will clear the sub path so we need to add it manually
but this is will be bad if you hard coded it
instead we will add it automatically
function YourPage() {
const nav = useNavigate()
const location = useLocation()
return (
<div>
<button onClick={() => nav(`${location.pathname}/yourTargetPath`)}>
Navigate
</button>
</div>
)
}
we will save our sub-path inside the variable and add it to navigate method
this should solve your problem

What are the advantages of configuration based routing over component based routing with react router 6.4+

Prior to react-router 6.4, I was happily declaring my routes using the <Routes> and <Route> components.
Wherever I wanted some route-dependent rendering, I could use these 2 components.
I was able to nest routes, again by using the <Routes> and <Route> components in a nested component. I was also able to even use multiple Routes containers next to each other in 1 component.
I like this a lot because it keeps things small, nested routes can be handled in the nested component and don't need to bloat the root component. But most of all I like it because seeing the routes in your code where they actually will be rendered makes the code very readable and easy to visualize for me.
Now with react-router 6.4 it seems like they are moving more to a configuration based style of routing, where you define all routes on a root level. In some interviews it's clear that the maintainers are proud that they can now define nested routes on a root level. https://reactrouter.com/en/main/start/overview#nested-routes
With that approach, you need to use the <Outlet/> component in spots where you want to render the nested route. When reading the code you to cross reference those Outlet locations with the configuration on the root level to figure out what is rendered where, which makes things much harder to visualize mentally.
So here's my question: what are the advantages of using such a configuration approach. Any guesses to why the maintainers are taking this road?
Clarifications
First, since the introduction of react-router#6 you could use a routes configuration via the useRoutes hook or just use the RRD components and define the JSX for them. This is the "configuration based" vs "component based" routing you describe. It's nothing new in RRDv6.4.
Configuration Example:
import { useRoutes } from 'react-router-dom';
const routes = [
{
element: <Layout />,
children: [
{
path: "/foobar",
element: <FooBar />
},
{
path: "/foo",
element: <Foo />
},
{
path: "/bar",
element: <Bar />
},
],
},
{
path: "/",
element: <Home />
},
];
const App = () => {
const appRoutes = useRoutes(routes);
return appRoutes;
};
Component Example:
import { Routes, Route } from 'react-router-dom';
const App = () => (
<Routes>
<Route element={<Layout />}>
<Route path="/foobar" element={<Foobar />} />
<Route path="/foo" element={<Foo />} />
<Route path="/bar" element={<Bar />} />
<Route>
<Route path="/" element={<Home />} />
</Routes>
);
I believe somewhere in the older RRD docs outright stated that the Routes component was implemented using the useRoutes hook under-the-hood by converting the JSX you pass as children into a routes configuration object.
Second, what you are describing as "nested routes" are actually called descendent routes. Nested routes on the other hand are Route components directly rendering other Route components that render their content into the Outlet of their parent route.
Descendent routes are good for code/route splitting.
Question
So here's my question: what are the advantages of using such a
configuration approach. Any guesses to why the maintainers are taking
this road?
What RRDv6.4 introduced was a new Data API which uses new Data Routers. See Picking a Router. Along with the new Data Routers came an updated Route component with a bunch of new props/functionality to be used within these new Data Routers. Now herein lies the rub. In order for the Route components to take advantage of the new Data APIs (action, errorElement, loader, and shouldRevalidate props) they necessarily need to be defined/configured when creating the Data Router. It doesn't matter if you use a routes "configuration" object with createBrowserRouter or use JSX with both createBrowserRouter and the createRoutesFromElements utility, all the routes that need the Data APIs need to be declared here.
The "advantage" is that you can use the new Data APIs with these routes. Of course the library maintainers are going to advertise and highlight their newest products and features.
AFAIK, the Data APIs don't currently work with descendent routes because these can be dynamic and there's not an easy way for a Route component to know what descendent Route components might be rendered under it in its sub-ReactTree at run-time. This is why nested routes are paramount; it's trivial to setup and maintain the overhead of the Data APIs when you know ahead of time exactly what routes can be rendered and what data they may manage.

React and Dynamic Routes including Filename and Extension

I am new to React and trying to put a CMS driven application together as kind of a POC. I am majorly stuck on how to get a full URL (including filename and ext) into a dynamic route in a React application. For example if I call this
http://localhost:8080/en/us/fly/index
The dynamic route picks it up. But if I call this
http://localhost:8080/en/us/fly/index.html
I get the following error. Cannot GET /en/us/fly/index.html
It is a requirement that I handle all URLs through my route even if they have a file extension & filename. My routes.jsx is as follows:
import React from 'react';
import {
Switch, Route
} from 'react-router-dom';
// Pages
import Page from "./pages/Page";
class Routes extends React.Component {
render() {
return (
<Switch>
<Route path="/:page" component={Page} />
</Switch>
)
}
}
export default Routes;
and my server.js is as follows:
const path = require('path')
const express = require('express');
const app = express();
const port = process.env.PORT || 8080;
// get an instance of router
const router = express.Router();
// serve static assets normally
router.use(express.static(__dirname + '/dist'))
router.get('*', function (request, response){
response.sendFile(path.resolve(__dirname, 'dist', 'index.html'))
})
// apply the routes to our application
app.use('/', router);
app.listen(port);
console.log('Site available on port ' + port);
How can I get all URLs to pass through my dynamic route? Is this even possible? I need to know if there are limitations in React that prevent this vs how it would work in a traditional ASP.Net MVC application where I can do this kind of dynamic routing.
Any help or advice on this will be very much appreciated as I am very stuck with this and don't fully understand the React world enough to find the solution.
Thanks in advance,
You can use *.* in your route. It matches with file extensions as below
<Route path="*.*" component={YOUR_COMPONENT}/> // matches /hello.jpg and /hello.html
Then you can access the props in your component as
// CMS name: sample.html
this.props.params.splat[0] // For name of prop without extension. It Will give 'sample'
this.props.params.splat[1] // For extension itself. It will give 'html'

How to make react router work with static assets, html5 mode, history API and nested routes?

I thought I was starting to understand React Router, but I hit a new wall when adding a library that loads css for its components. Everything works fine when a navigate from my home, to the page containing the component, but when I refresh it, the urls of the fonts are broken...
I found some pointer here and here but no luck so far. Is this a common issue ? How to work around it ?
I use webpack dev server with default config built by a yeoman scaffolder.
The library I use is React Fa to display icons.
When I load my app on http://localhost:8000/ everything displays fine, then I navigate to http://localhost:8000/customer/ABCD1234/chat and my icons are ok. The font was loaded properly.
Then I refresh the page, and I see in the console :
DOMLazyTree.js?019b:56 GET http://localhost:8000/customer/ABCD1234/assets/926c93d201fe51c8f351e858468980c3.woff2
Which is obviously broken because the customer part shouldnt be here...
Here is my router so far :
ReactDOM.render(
<Router history={browserHistory}>
<Route path='/' component={App}>
<IndexRoute component={Index}/>
<Route path='customer/:id' component={Customer} />
<Route path='customer/:id/chat' component={CustomerChat}/>
<Route path="*" component={ NotFound } />
</Route>
</Router>
, document.getElementById('support-app'));
I also tried adding a <base href="/"/> to my index.html, but I get a nice warning in red in the console, so maybe not the best idea :
Warning: Automatically setting basename using is
deprecated and will be removed in the next major release. The
semantics of are subtly different from basename. Please
pass the basename explicitly in the options to createHistory
It sounds like you have defined relative paths to your assets and icons in your CSS.
background-image: url('assets/image/myicon.png')
instead, try an absolute path:
background-image: url('/assets/image/myicon.png')
As React apps are single page applications, and you enter your app on /, all urls are going to work fine no matter the page you navigate to, since they are relative to the root where you started.
But as soon as you reload your page on a different path such as /customer/ABCD all your assets are going to be relative to that, hence the wrong url you see in your console.
Also, if using webpack, you can try to set your publicPath in your config file like this:
...
output: {
path: 'assets',
publicPath: '/'
},
...
I hava a same problem.
Add a <base href="http://whatever"> tag to the head of a page containing react-router's HTML using the default browserHistory
Load the page - history.js will output 2 errors, Warning: Automatically setting basename using <base href> is deprecated and will be removed in the next major release. The semantics of are subtly different from basename. Please pass the basename explicitly in the options to createHistory
Change the react-router history to use history where history is const history = useRouterHistory(createHistory)({ basename: 'http://whatever' }), which should fix the problem (as basename is now explicitly passed)
Reload the page
https://github.com/reactjs/react-router/issues/3387
5/9 update
In my case.
index.html
<head>
<base href="/" />
<script src="app.js"></script>
</head>
<body>
<div id="app">
</div>
</body>
app.js
import { Router , useRouterHistory } from 'react-router'
import { createHistory } from 'history'
const browserHistory = useRouterHistory(createHistory)({ basename: '/' })
render(
<Router routes={routes} history={browserHistory}/>,
document.getElementById("app")
);
And then reload the warning to disappears.
hope this is useful.
I solved this thanks to react-script:2+
You just have to create a js file called setupProxy.js, it will be loaded by the developement server. Now you have complete control over your proxy:
const proxy = require('http-proxy-middleware');
module.exports = function(app) {
console.log("configuring development proxies to point to localhost:4000")
app.use(proxy('/api', { target: 'http://localhost:4000/' }));
app.use(proxy('/graphql', { target: 'http://localhost:4000/' }));
};
https://github.com/facebook/create-react-app/blob/master/packages/react-scripts/template/README.md#configuring-the-proxy-manually
Edit:
The old link broke, this is an updated one:
https://create-react-app.dev/docs/proxying-api-requests-in-development/#configuring-the-proxy-manually
I solved this by loading font awesome cdn on the app index page. Then it is available on all routes in all situations.

How to let react router respond with 404 status code?

I'm using react router as root and all requests under "/" are directed to react router. And when react router found that the url is not matched with any of the defined components, it renders with NoMatch component. And here goes the problem, NoMatch is rendered and that's what I want, but the status code is still 200 instead of 404. And when my css or js files are placed with a wrong url react router does the same thing, it responds with 200! And then the page tells me that there's some problem with my resources content type!
So, how can I use react router to handle everything in the "/" and still get it to treat 404 errors right(to respond with 404 status code)?
code in react router
render((
<Router history={browserHistory}>
<Route path="/" component={App}>
<IndexRoute component={Index}/>
<Route path="archived" component={App}>
<IndexRoute component={ArchivedPage}/>
<Route path="project/:projectId" component={ArchivedDetailPage}/>
</Route>
<Route path="*" component={NoMatch}/>
</Route>
</Router>
), document.getElementById('app'));
the servre side
router.use('/', function(req, res, next) {
res.render('index-react', {
title: 'some title'
});
});
With react-router 2.0.0 you can do:
<Route path="*" component={NoMatch} status={404}/>
EDIT:
What you would need to do, is to create a custom attribute on your route definition, like you can see above (status).
When you are about rendering you component on server side, check on this attribute and send a response with a the code of this attribute:
routes.js
import React from 'react';
import {IndexRoute, Route} from 'react-router';
import {
App,
Home,
Post,
NotFound,
} from 'containerComponents';
export default () => {
return (
<Route path="/" component={App}>
<IndexRoute component={Home} />
<Route path='post/' component={Post} />
{ /* Catch all route */ }
<Route path="*" component={NotFound} status={404} />
</Route>
);
};
server.js:
import { match } from 'react-router';
import getRoutes from './routes';
....
app.use((req, res) => {
match({ history, routes: getRoutes(), location: req.originalUrl }, (error,
redirectLocation, renderProps) => {
if (redirectLocation) {
res.redirect(redirectLocation.pathname + redirectLocation.search);
} else if (error) {
console.error('ROUTER ERROR:', error);
res.status(500);
} else if (renderProps) {
const is404 = renderProps.routes.find(r => r.status === 404) !== undefined;
}
if (is404) {
res.status(404).send('Not found');
} else {
//Go on and render the freaking component...
}
});
});
Sorry about that... certainly my solution wasn't working by itself, and I missed the proper piece of code where you actually check on this attribute and render accordingly.
As you can see, I just send the 'Not found' text to the client, however, it would be best if you catch the component to render from renderProps (NoMatch in this case) and render it.
Cheers
I did some digging, this is how we do things in the v4 release.
<Route
render={({ staticContext }) => {
if (staticContext) {
staticContext.statusCode = 404
}
return <NotFound />
}}
/>
The Route component is used to access the staticContext which has a statusCode prop that you can set if you use the StaticRouter component to render on the server.
Server code looks something like this (with some TypeScript typing)
const currentLocation: Location = {
pathname: req.pathname,
search: req.search,
hash: '',
state: undefined
}
const staticRouterContext: StaticContext & StaticRouterContext = {}
ReactDOMServer.renderToString(
<StaticRouter location={currentLocation} context={staticRouterContext}>
...
</StaticRouter>
)
if (staticRouterContext.statusCode) {
res.status(staticRouterContext.statusCode)
}
Note: I think the typings are a bit wonky because the StaticRouterContext doesn't have the statusCode prop from the StaticContext interface. Probably a mistake in the typings. This works just fine though.
Zen: What is the error code of a silent network?
I puzzled on this question as well. The odd case, you don't change the HTTP error code. We think we should, because a random URL was called, but why?
In a SPA (Single Page Application), there isn't network traffic but just switching of panes. The HTTP error code is in the headers for serving a new page, while all the content is in a named <div> that doesn't reload a new page. One would need to toss out page, transmit and render a new page to have the 404 return code, and then send the original page again when the user moves away.
And, its only the user. The 404 error code, or any other HTTP code, is really a hint to the browser or service. If the requester is a browser, the human is given a better indication than the browser could provide. If the requestor is a crawler, it probably scans for 404 in <h1>'s. If the requester is using your website as an API, why is that a good thing?
So the answer is that we set the HTTP status codes out of habit. Don't.
To do this, you need to run the match on the server as well, to see if a route matches. Of course, if you're going to do this, you may well do full-fledged server-side rendering as well! Consult the server rendering guide.

Categories

Resources