I am working to learn React. The sample project was designed to be used with React 0.13.3 and react-router 0.13.3. I am using react 15.4.1 and react-router 3.0.0
The sample project uses willTransitionTo to do a simple confirmation before allowing the user to navigate to a page. here is the code:
var About = React.createClass({
statics: {
willTransitionTo: function(transition, params, query, callback) {
if (!confirm('Are you sure you want to read a page that\'s this boring?')) {
transition.about();
} else {
callback();
}
}, ...
I know in the version of react-router I am using, the above no longer works. So following the auth-flow example found on the react router docs page I converted my code to use the onEnter convention. here is what I have thus far:
function confirmTransition(nextState, replace){
if(comfirm('Are you sure you want to read a page that\'s this boring?')){
replace({
pathname: '/about',
state: {nextPathname: nextState.location.pathname}
});
}
}
ReactDOM.render((
<Router history={hashHistory}>
<Route path="/" component={require('./components/app')}>
<IndexRoute component={require('./components/homePage')} />
<Route path="/authors" component={require('./components/authors/authorPage')} />
<Route path="/about" component={require('./components/about/aboutPage')} />
<Redirect from="about-us" to="about" />
<Route path='*' component={require('./components/notFoundPage')} onEnter={confirmTransition}/>
</Route>
</Router>
), document.getElementById('app'));
The issue I'm having is when I try to navigate to the about page, the onEnter does not fire.
I'm certain I am missing something. What might I be doing wrong?
Ok. I'm... well not intelligent. I found I had placed the onEnter on the wrong route.
Thank you all for not pointing that out.
Related
My app has a default route that loads the component HomePage on the localhost/ path in dev mode.
But when I refresh the app from any page, the home page is displayed instead, even though the URL in my browser displays localhost/any/other/path. If I then navigate manually through the app to the /any/other/path path, the browser's URL becomes localhost/any/other/path/any/other/path.
It's like the router always thinks the current path is / on refresh.
What could the issue be? I'm using React 16 and "react-router-dom": "5.1.2".
My routes are laid out as follows:
<Provider store={store}>
<ConnectedRouter>
<div>
<Switch>
<Route exact path="/" component={HomePage} />
<Route exact path="/login" component={LoginPage} />
<Route exact path="/proxy" component={ProxyPage} />
...
</Switch>
</div>
</ConnectedRouter>
</Provider>
And my Redux router reducer is as follows:
import { createBrowserHistory } from 'history'
const history = createBrowserHistory({
basename: window.location.pathname
})
// Reducers
combineReducers({
...
router: connectRouter(history),
...
})
In the store the router reducer starts as follows, even when loading a URL:
router: {
action: "POP",
location: {
hash: "",
pathname: "/",
query: { },
search: "",
state: undefined
}
}
Edit: Simplified my use case to be more straightforward, added more information
I am not 100% sure, but my hunch is that this behavior is more related to using Redux and ConnectedRouter than your routes, which look fine to me. 2 questions:
Are you using hot reloading?
If you take this same code and run it in a non-redux app with BrowserRouter, what happens?
When true, redirecting will push a new entry onto the history instead of replacing the current one.
Redirect push to="/somewhere/else" /
Are you using the Redirect push in your code?
Found the issue.
I had changed the default basename argument in my browserHistory's configuration, which did not work as I understood (I was trying to fix a lack of redirection issue on app launch).
Removing the basename: window.location.pathname line fixed my (self-inflicted) problem.
<Provider store={store}>
<ConnectedRouter>
<div>
<Switch>
<Route exact path="/">
<Redirect to="/home" />
</Route>
<Route exact path="/login" component={LoginPage} />
<Route exact path="/proxy" component={ProxyPage} />
...
</Switch>
</div>
</ConnectedRouter>
</Provider>
I'm trying to implement React Router with query params like so http://localhost:3000/login?Id=1, I was able to achieve it only for my login route that too if I put path as http://localhost:3000/ which then redirects , however, I want to implement across the application. It matches nomatch route if I implement on other routes. This is how my index.js looks like, Can someone guide me how can i go about implementing all routes path including query params ?.
ReactDOM.render(
<BrowserRouter>
<Switch>
<Route
exact
path={`/`}
render={() => {
if (!store.getState().login.isAvailable) {
return <Redirect to={`/login?Id=${Id}`} />
} else {
return <Dashboard />
}
}}
/>
<Route exact path={`/login`} component={Login} />
<Route exact path={`/signup`} component={SignUp} />
{Routes.map((prop, key) => (
<Route path={prop.path} key={key} component={prop.component} />
))}
<Route component={NoMatch} />
</Switch>
</BrowserRouter>,
document.getElementById('root')
)
There are two ways about to accomplish what you want.
The most basic way would be on each "page" or root component of each route, handle the parsing of query params.
Any component that is the component of a Route component, will have the prop location passed to it. The query params are located in location.search and that will need to be parsed. If you are only worried about modern browsers, you can use URLSearchParams, or you can use a library like query-string, or of course, you can parse them yourself.
You can read more in the react-router docs.
The second way of doing this, really isn't that different, but you can have a HOC that wraps around each of your "pages" that handles the parsing of the query params, and passes them as a list or something to the "page" component in question.
Here's an example of the basic way using URLSearchParams:
import React from "react";
import { BrowserRouter as Router, Route, Link } from "react-router-dom";
// this is your "page" component, we are using the location prop
function ParamsPage({ location }) {
// you can use whatever you want to parse the params
let params = new URLSearchParams(location.search);
return (
<div>
<div>{params.get("name")}</div>
// this link goes to this same page, but passes a query param
Link that has params
</div>
);
}
// this would be equivalent to your index.js page
function ParamsExample() {
return (
<Router>
<Route component={ParamsPage} />
</Router>
);
}
export default ParamsExample;
EDIT: and to clarify, you don't need to do anything on your index.js page to make this work, the simple Routes you have should work fine.
I am using react and redux for my application.
I have my routes defined as follows
<HashRouter>
<div className="container">
<Route path="/register" component={Register} />
<Route path="/login" component={Login} />
</div>
</HashRouter>
And I have defined my components as
export default connect(mapStateToProps)(Login);
export default connect(mapStateToProps)(Register);
Now once the registration is done I want to redirect the user to login page. So when the login is successful I am redirecting him to login from my UserAction function as follows
UserService.register(user)
.then(
user => {
dispatch(success());
history.push('/#/login'); //<== here is the issue
},
error => {
dispatch(failure(error));
}
);
The history.push above changes the URL but doesn't load the Login component.
I also tried
export default withRouter(connect(mapStateToProps)(Login));
export default withRouter(connect(mapStateToProps)(Register));
But still no luck. Any solution how to fix this?
Note: I am not using react-router-redux
Why do you even want to change the route with history push?
It easily can be done via location.hash method ( since you use HashRouter ) and of course the browser and the react itself is enough intelligent to save it in the history of the browser.
so whenever you want to change the url just use something like :
location.hash = "/myroute/10/secondroute"
and remember that you can use <Link> component to change the url using react-router itself and not bothering yourself to manage the hash or the history.
Link provides declarative, accessible navigation around your application.
Usage:
import { Link } from 'react-router-dom'
<Link to="/about">About</Link>
OR
<Link to={{
pathname: '/courses',
search: '?sort=name',
hash: '#the-hash',
state: { fromDashboard: true }
}}/>
More informations
Have you tried :
this.context.router.push('/page');
At least can you tell me what's your redux-router version ?
In your index file I suggest you that :
import routes from './routes'
ReactDOM.render((
<Provider store={store}>
<Router history={hashHistory} routes={routes}/>
</Provider>), document.getElementById('root')
);
And in your route file :
<Route path="/" component={App}>
<IndexRoute component={requireAuth(Home)}/>
<Route path="signup" component={SignUp}/>
</Route>
Do you have errors in your console ?
When setting a react-router app, I often set it on callbacks to represent authorization filters. this leads me to such designs:
# before, in my root component
requireAuth() { ... }
noAuth() { ... }
render() {
return (
<Router history={this.props.history}>
<Route path='/' component={App} onEnter={this.requireAuth}>
<Route path='toys' component={Toys}/>
...
<Route path='/auth' component={Auth} onEnter={this.noAuth}>
...
</Router>
)
}
I am now trying to port this to nodejs and render the page in the server. Suddenly, I'm asked to group all the routes in a const. Second, I lose my root component and those this. callback binds, and am not able to recreate the same architecture on the server.
So my first problem is the route. In react, I can't return groups of components, but a component which encapsulates all the rest. So, this was my attempt:
# root component render
# after import routes from './routes'
render() {
return (
<Router routes={routes} history={this.props.history}/>
);
}
# in routes.js
module.exports = (
<Route> # is this right?
<Route path='/' component={App} onEnter={this.requireAuth}>
<Route path='toys' component={Toys}/>
...
<Route path='/auth' component={Auth} onEnter={this.noAuth}>
...
</Route>
So, this doesn't seem to cut it and I get a few errors. First, I'm encapsulating all routes inside a main Route component. Is this correct, from a react-router perspective? Can I have kind of empty-encapsulating components?
Second would be: How do I bind those this. callbacks on the server side now? Since I'm following the tutorials, My express router looks smth like this:
import routes from './src/components/routes';
...
app.get('*', (req, res) => {
match(
{ routes, location: req.url },
(err, redirectLocation, renderProps) => {
....
But it's breaking with:
onEnter: undefined.requireAuth,
^
TypeError: Cannot read property 'requireAuth' of undefined
Which is right, as routes constant is not bound to any context.
Or is there a more proper way to do this in react-router, and binding callbacks to routes is the wrong approach?
I am currently using React Router and have routes that use the browserHistory, for example:
import { Router, Route, IndexRedirect, browserHistory } from 'react-router';
<Router history={browserHistory}>
<Route path="tasks" component={Tasks}>
<IndexRoute component={List} />
<Route path="/:id" component={Item} />
</Route>
</Router>
Which allows me to do routes such as:
/tasks/
/tasks/1234
This works, but we have come across a situation where we have two views that are displayed at the same time. We'd like for the link to be shareable and have the app open with both views.
So for example, if we have tasks on the left side of the screen, and a shop on the right, we'd like for there to be two independent parts of the path, something like:
/tasks/1234/#/shop/item/xyz
The shop route should be independent of the left of the hash, and the tasks route should be independent of the right of the hash, so that /new-feature/xyz/#/shop/item/xyz should still render the same view on the right side of the window.
Is it possible to have React Router do routes like this? Or will I have to write a custom router to solve this?
I'm guessing I'd basically have to combine the browserHistory and hashHistory together, but I don't think that's possible with React Router out of the box.
I think rolling your own router just to handle this case might be a little overboard. You can have as many different paths in your route config as you want, and access param information in your components:
<Router history={browserHistory}>
<Route path="/" component={App} />
<Route path="/tasks/:taskId" component={App} />
<Route path="/shop/:shopId" component={App} />
<Route path="/tasks/:taskId/shop/:shopId" component={App} />
</Router>
let App = ({ params }) => // simplified
<div>
{ params.shopId && <Shop /> }
{ params.taskId && <List /> }
</div>
Just a thought.. I think there are several ways to augment this to handle more complex scenarios but this would definitely work for what you've specified in the question (for the most part).
Update: Above is 'hardcoded', but of course you do not need to write out every combination by hand. This is what loops are for.
import * as routeComponents from './routeComponents'
<Router history={browserHistory}>
{ routeComponents.map(r => <Route {...r} />) }
</Router>
let App = ({ params }) =>
<div>
{ routeComponents.reduce((matches, r) => ([
...components,
...(params[r.slug] ? [r.component] : [])
]), [])}
</div>
We have developed our own router Boring Router with "parallel routing" support, though it depends on MobX and works differently with React Router in many ways.
In Boring Router, a parallel route is stored as query string parameter prefixed with _. E.g., /app/workbench?_sidebar=/achievements.
Check out a complete example here.