I'm using react-router-dom for my routing and, since I'm also using GitHub Pages, I need to use HashRouter in my Router.jsx like so
import React from 'react';
import { HashRouter as Router, Route, Switch } from 'react-router-dom';
import Home from './Home';
import Customer from './Customer';
const MyRouter = () => (
<Router>
<Switch>
<Route path="/customer" component={Customer} />
<Route path="/" component={Home} />
</Switch>
</Router>
);
export default MyRouter;
In my Home.jsx component I have defined my propTypes like so
Homepage.propTypes = {
history: PropTypes.shape({ // set by react-router
push: PropTypes.func.isRequired,
}).isRequired,
};
My problem is that everytime I get a # in my URL and I would like to know why is it there all the time and why does my localhost without # redirect me to the same URL but with #(like if I go to http://localhost:4000/myApp/ it will redirect me to http://localhost:4000/myApp/#/). I would like to get rif of it for tracking purposes. I've tried using BrowserRouter but it won't work, as well as the history parameter for the Router like history={createHashHistory({ queryKey: false })} or history={browserHistory}.
Thank you very much (and sorry for my English)
Due to the front end nature of your client side React application, routing is a bit of a hack. The functionality of the two main router options are as follows :
HashRouter uses a hash symbol in the URL, which has the effect of all subsequent URL path content being ignored in the server request (ie you send "www.mywebsite.com/#/person/john" the server gets "www.mywebsite.com". As a result the server will return the pre # URL response, and then the post # path will be handled by parsed by your client side react application.
BrowserRouter will not append the # symbol to your URL, however will create issues when you try to link to a page or reload a page. If the explicit route exists in your client react app, but not on your server, reloading and linking(anything that hits the server directly) will return 404 not found errors.
The solution to this problem can be seen on this extensive post : https://stackoverflow.com/a/36623117/2249933
The main principle is that you match your client side routes, with routes on your server, that way allowing clean url's, but without the limitations of browser router on it's own.
I faced this issue today. I was trying to learn dynamic routing and I used hashRouter first. After a while, I want to get rid of hash sign and then learned that all purpose of the hashRouter is completely different than what I am trying to achieve.
But converting current hashRouter was easy if you understand your current structure.
I am using webpack and testing them on webpack-dev-server, I have lots of nested routes and this method is working for me.
import { createBrowserHistory } from "history";
import { Route, Switch, Redirect, BrowserRouter } from "react-router-dom";
const hist = createBrowserHistory();
<BrowserRouter history={hist}>
<Switch>
<Route path="/" component={ComponentName} />
</Switch>
</BrowserRouter>
Note: Addition to that for webpack there are some configs to add like follows;
In webpack.config.js
output: {
...
publicPatch: "/",
...
}
devServer: {
...
historyApiFallback: true,
...
}
As I understand,
publicPath resolve finding chunked code connections with giving them
predetermined path (which is an issue on dynamic routing to call
nested routed pages)
historyApiFallback is the magic in here. Nested routes don't connect to browserHistory, when you are trying to request the link since this is client-side routing, it returns blank. But if you put historyApiFallback config, dev-server returns all unknown links to index.html. That means all nested routes links are treating as unknown, but since it loads index.html to the user. User can access the pages with their determined route addresses.
HashRouter ... is navigation via everything after the # (called a hash).
https://github.com/ReactTraining/react-router/blob/master/packages/react-router-dom/docs/api/HashRouter.md
If you don't want that, you can try BrowserRouter - edit: I see you said BrowserRouter doesn't work. In what way?
Related
My Meteor/React application should render one static page besides the reactive one pagers with reactive UIs. The static package does not even need to be "hydrated" with the React magic after displayed in the browser. Though the server-side rendering on the server will be dynamic with React components.
I got it working, but I'm not sure if it is the intended official way to do it.
File import/client/routes.js
...
<Route path="/reactive/pages/:id" component={ReactiveComponent} />
<Route path="/static_url" />
...
File server/main.jsx
...
onPageLoad((sink) => {
if (sink.request.path === '/static_url) {
sink.renderIntoElementById('app', renderToString(
<StaticPage />,
));
}
});
...
File client/main.js
...
import { Routes } from '../imports/client/routes';
Meteor.startup(() => {
...
if (window.location.pathname !== '/offer_pdf') {
render(Routes, document.getElementById('app'));
}
});
...
Especially when rendering dependent on the URI, it seems a little bit hacky to me. Does a more elegant solution exist?
I don't think there is anything official, but in general, of course, it's a good idea to use a router for rendering different pages, so I thought it worth pointing out that you can use react-router on the server as well:
import React from "react";
import { renderToString } from "react-dom/server";
import { onPageLoad } from "meteor/server-render";
import { StaticRouter } from 'react-router-dom';
import App from '/imports/ui/app.jsx';
onPageLoad(sink => {
const context = {};
sink.renderIntoElementById("app", renderToString(
<StaticRouter location={sink.request.url} context={context}>
<App />
</StaticRouter>
));
/* Context is written by the routes in the app. The NotFound route, used
when, uhm, no route is found, sets the status code. Here we set it on the
HTTP response to get a hard 404, not just a soft 404. Important for Google bot.
*/
context.statusCode && sink.setStatusCode(context.statusCode);
// add title to head of document if set by route
sink.appendToHead(`<title>${context.title || 'My page'}</title>`);
});
In App you can then use the usual Switch and Route tags for specifying different routes. In that you could, for instance, only specify routes that you want to be server-rendered.
I've made two components. one is Articles.js and the another one is article.js. The router is in app.js. here is code from app.js:
<Router>
<Switch>
<Route exact path="/articles" component={Articles}>
</Route>
<Route path="/article/:id" component={Article}/>
</Switch>
</Router>
I've made link to "article/:id" in Articles component. If I click on the link in articles page, it works just fine, however, If I try to reload the page or manually enter id, for eg: "article/23", it will not render anything at all on the page.
I found some results on internet, but, most of them are either not relevant or are using hooks, which I cannot use with class components.
In componentDidMount function of Article compnent i am calling getData function which fetches data from the server and then after verfiying the response sends data here to this function :
initFunction = (ar)=>{
let data = ar.map(d=>{
return(
<tr><td>{d.id}</td><td>{d.title}</td><td>{moment(d.created_on).format('MMMM,Do YYYY')}</td><td>
<Link to={`article/${d.id}`}>Edit</Link> |
<Link to={`article/delete/${d.id}`}> Delete</Link>
</td></tr>
)
})
this.setState({
tableData:<>{data}</>
})
}
And this function just generates table rows and save them in state, which I use in render function to display the data. If I go to article page through this link, it will work fine but same link will not render anything if type it manually or reload it.
Here are the components I am importing in my component:
import ReactPaginate from 'react-paginate';
import { Link } from 'react-router-dom';
import { instanceOf } from 'prop-types';
import { withCookies, Cookies } from 'react-cookie';
import moment from 'moment'
The issue here is probably that your server is not set up to serve your app on the /article route.
When you click on the link in your app then react-router does not actually request make a request to your server for that route. Instead it updates the url and re-renders your app which picks up the new route.
When you load the route directly (such as on page reload or manually typing in the url bar) the react router has not been loaded yet. Instead the browser just blindly makes a request to that route. This is when your app is failing.
I had the same issue as you. I found the solution from this thread.
react-router dynamic segments crash when accessed
added into the of my index.html
what is initState of id, I think you need to store id to state and set initState is empty string, it work for me.
const [myId, setMyId] = useState("")
I am not able to render any component on any route other than "/".
Here is my code for routes.js
import React from 'react'
import ReactDOM from 'react-dom';
import {
BrowserRouter as Router,
Route,
} from 'react-router-dom'
import StockTable from './containers/stockTableContainer';
import StockDetail from './containers/stockDetailContainer';
export const getRoutes = (store) => {
return(
<Router>
<div>
<Route exact path ='/' component ={StockTable}/>
<Route path ='/details' component ={StockDetail}/>
</div>
</Router>
)
}
I can render the StockDetail component on "/" route but i can't route it on "/details".
I have also tried using but still couldn't render "/details"
full code at : https://github.com/shrutis18/stockApp
If your application is hosted on a static file server, you need to use a <HashRouter> instead of a <BrowserRouter>.
This is because your server doesn't know how to handle requests made to a path other than /. For the BrowserRouter to work, any routable request should be served the index.html.
An excerpt from the FAQ
When you load the root page of a website hosted on a static file
server (e.g., http://www.example.com), a <BrowserHistory> might appear
to work. However, this is only because when the browser makes the
request for the root page, the server responds with the root
index.html file.
If you load the application through the root page, in-app navigation
will work because requests are not actually made to the server. This
means that if you load http://www.example.com and click a link to
http://www.example.com/other-page/, your application will match and
render the /other-page/ route.
However, you will end up with a blank screen if you were to refresh a
non-root page (or just attempt to navigate directly to it). Opening up
your browser's developer tools, you will see an error message in the
console informing you that the page could not be loaded. This is
because static file servers rely on the requested file actually
existing.
Further along the lines
This is not an issue when your server can respond to dynamic requests. In that situation, you can instruct the server to catch all requests and serve up the same index.html file.
You can use the Switch to your route as directed in the docs
<Switch>
<Route path='some_path' component={Some component} />
</Switch>
https://github.com/ReactTraining/react-router/blob/master/packages/react-router-dom/docs/guides/basic-components.md
I have paths that I want React Router to handle, and I also have an express API backend that I call from the React app to do some secure API calls.
/#/ - want app served here
/#/:id - unique URLs for the app, i use the ID to call certain express endpoints from React.
* - all express REST endpoints
Express app.js:
app.use(middleware);
app.use(require('webpack-hot-middleware')(compiler, {
log: console.log
}));
app.use('/api', [router_invokeBhApi]);
app.get('/#/*', function response(req, res) {
res.write(middleware.fileSystem.readFileSync(path.join(__dirname, 'dist/index.html')));
res.end();
});
app.use(express.static(path.join(__dirname, '/dist')))
My React router component:
export default (
<Route path="/" component={App}>
<IndexRoute component={HomePage} />
<Route path="/:id" component={ConsentForm} something="dope"/>
</Route>
);
So here's what's happening:
- going to localhost:8000 serves the app with the HomePage component
- going to localhost:8000/#/ also serves the app with the HomePage component
- going to localhost:8000/example gives me Cannot GET /example which means express is working
- going to localhost:8000/api gives me a test JSON object which i send from express, which is correct.
- going to localhost:8000/#/somehashcode STILL gives me the HomePage component, when it SHOULD be giving me the ConsentForm component.
I inspected the Router component using React Dev tools:
- the RouterContext component has an object called routes with a childRoutes inside the routes[0], and it has the path /:id. routes[0] also has a path / which tells me React Router loaded all the routes correctly??
So confused...
React file where I render the whole app:
import 'babel-polyfill';
import React from 'react';
import ReactDOM from 'react-dom';
import { Router, browserHistory } from 'react-router';
import Routes from './shared/components/Routes';
import './shared/base.css';
import './shared/customInput.css';
const ROOT_ELEMENT = 'app';
ReactDOM.render((
<Router history={browserHistory}>
{Routes}
</Router>
), document.getElementById(ROOT_ELEMENT));
I finally figured it out!
In the React Router component change the :id route to:
<Route path="/redirect/:id" component={ConsentForm} something="dope"/>
In Express, app.js change the React serve to:
app.get('/redirect/*', function response(req, res) {
res.write(middleware.fileSystem.readFileSync(path.join(__dirname, 'dist/index.html')));
res.end();
});
Learnt 2 things from this:
- Using # in the route doesn't work. Not sure why.
- Explicitly state the route in the React side as well.
Quite weird, or maybe I'm going off some half-baked knowledge. Any resource to point me in the right direction would be so awesome.
Pretty sure someone else is going to run into this as they use Express and React together.
Found your problem.
You're using BrowserHistory in your configuration. This option (the recommended one at present) does not use a hash in the uri, but instead directly integrates with the browser.
Try hitting your app at localhost:8000/somehashcode and see what you get.
I am not sure how to get clean url with react router.
For the moment I have this:
http://localhost:8889/#/myRoute?_k=qq67x0
I would like to have this:
http://localhost:8889/myRoute
Is there a particular configuration step that I should set to fix this?
This is how I initialize the router:
import { browserHistory, Router, Route, Link, IndexRoute } from 'react-router
And here is my render function:
render((
<Router history={browserHistory}>
<Route path="/" component={App}>
<IndexRoute component={MyComponent2} />
<Route path="myComponent1" component={MyComponent1} />
<Route path="myComponent2" component={MyComponent2} />
</Route>
</Router>
), document.getElementById('react-container'))
EDIT:
I have installed the last version of the router and now it works as expected.
Thanks!
Take a look at the documentation under "What is that ?_k=ckuvup junk in the URL?":
When a history transitions around your app with push or replace, it can store "location state" that doesn't show up in the URL on the new location, think of it a little bit like post data in an HTML form.
The DOM API that hash history uses to transition around is simply window.location.hash = newHash, with no place to store location state. But, we want all histories to be able to use location state, so we shim it by creating a unique key for each location and then store that state in session storage. When the visitor clicks "back" and "forward" we now have a mechanism to restore the location state.
The history package docs explain how to opt out, if you want to continue to use the hash history:
If you prefer to use a different query parameter, or to opt-out of this behavior entirely, use the queryKey configuration option.
import createHistory from 'history/lib/createHashHistory'
// Use _key instead of _k.
let history = createHistory({
queryKey: '_key'
})
// Opt-out of persistent state, not recommended.
let history = createHistory({
queryKey: false
})
If you want to use the HTML 5 pushState API, as you mentioned in your question, then you should use browserHistory in your Router configuration instead of hashHistory:
import { Router, browserHistory } from 'react-router'
<Router history={browserHistory}>
...
She the full "Histories" page in the React Router docs for more information.