I have a couple of routes I want to show only when a certain condition is met. If this condition is met, and the routes are enabled navigating to them through the URL is not possible, and the Redirect always gets hit
constructor(props: {}) {
super(props);
this.state = {
configuration: new Configuration({}),
}
async function GetConfiguration() {
try {
var response = await fetch("/getconfiguration");
return await response.json();
}
catch (error) {
console.log(error);
}
}
async componentDidMount() {
...
var configuration = await GetConfiguration();
this.setState({ configuration: configuration });
...
}
render() {
...
<Router>
<Switch>
<Route exact path="/my-account">
<div className='dw-side-menu'></div>
...
</Route>
{this.state.configuration.shouldRoute && <Route exact path="/my-company"><div>my company</div></Route>}
{this.state.configuration.shouldRoute && <Route exact path="/user-management"><div>user management</div></Route>}
<Redirect to="/my-account" />
</Switch>
</Router>
...
}
The awkward thing is that, when I click on the Link somewhere else on the page, the routing to, e.g. /my-company, works - but not if I type the URL into the browser it only goes to the redirect, as if the routes are not there at all. Also if I hit refresh when on /my-company I get redirected back to the my-account. Without the conditions everything is working fine.
Without Redirect, entering the URLs and Refreshing the browser works as expected, just that I don't get redirected when a route is not recognized.
What am I doing wrong?
As suspected in my comment, this.state.configuration.shouldRoute is undefined on first render.
One workaround for this would be to actually display a loading state until you have loaded the configuration, like so:
// in render(), before your other return
if(Object.keys(this.state.configuration).length === 0){
// config has not been loaded yet
return (<MyCoolLoadingComponent />)
}
If displaying a loading indicator before the config is loaded is an option, go with it. If it is not, you have to rethink your architecture.
To make your code cleaner, with less bugs, put sections of codes into individual components. Then you could say:
class SideMenu extends component {
render(
<div className='dw-side-menu'></div>
...
)
}
class MyCompany extends component {
render(
<div>my company</div>
...
)
}
class UserManagement extends component {
render(
<div>user management</div>
...
)
}
render() {
...
<Router>
<Switch>
<Route exact path="/" component={HomePage} />
<Route path="/my-account" component={MyAccount} />
<Route path="/my-company" component={MyCompany} />
<Route path="/user-management" component={UserManagement} />
<Redirect to="/my-account" />
</Switch>
</Router>
...
}
Cleaner code with less issues. Hope this helps. You can as well use some of the powerful features in react-router-dom; location, history and match. Thanks.
Related
I am having a react-redux app and react-router v4 inside of app
Is there a way to catch all invalid URLs that were entered and save them to an array, like so ['https://mysite.co/progects', 'https://mysite.co/sometypo', 'https://mysite.co/something']?
And then I want to send that data to server for building some redirects and some sitemap
Currently I have this:
<Switch>
{/* <Route path='/blog' exact component={Blog} /> */}
<Route path='/projects/:id' component={ProjectDetails} />
<Route path='/career/:id' component={CareerDetails} />
<Route path='/apply-for-job' render={(props) => (
<ModalWindow
{...props}
modalHeader='Apply form'>
<ApplyForm history={props.history} />
</ModalWindow>
)} />
<Route exact path='/' component={withScrollPreservation(LandingPage)} />
<Route component={NoMatch} />
{/* <Route component={withScrollPreservation(LandingPage)} /> */}
</Switch>
In your NoMatch component, you can have the logic to update unmatched/incorrect urls
class NoMatch extends React.Component {
componentDidMount() {
const { addNoMatchUrl } = this.props;
// you might want to handle the duplicate url logic here too in addNoMatchUrl method
addNoMatchUrl(this.props.location.pathname);
}
componentDidUpdate(prevProps) {
const { location, addNoMatchUrl } = this.props;
if (location.pathname !== prevProps.location.pathname) {
addNoMatchUrl(location.pathname);
}
}
render() {
// render logic
}
}
export default connect(null, {addNoMatchUrl});
If you want, say, to redirect someone, who typed '/progects' to '/projects' - well, that's nice UX, but your Switch block will be cluttered with tens of possible invalid urls.
As I see it, maybe you should add <Redirect to='/main' /> at the bottom of your Switch so any invalid url gets redirected to Main component (or whichever you have) or to 404-Component.
If you still want to gather them, then instead of redirecting to Main or 404 Component, send them to specific Error component, where you can get the link via this.props.history.location and handle that link further in the component: send to server, set that url in local/session storage, etc.
Note that you'll need a way to store that data someplace which won't get cleared on unmounting.
In order to send users to that route you'll need to place that at the bottom of your Switch.
<Switch>
...{your routes}
<Route component={Error} />
</Switch>
So actually all you need to do is handle those urls in your NoMatch component, like so
const path = this.props.history.location;
axios.post('your api', path);
I have a React app that has a basic structure like seen below. I am attempting to programmatically redirect the user to /profile route but I am seeing Warning: You tried to redirect to the same route you're currently on: "/profile".
Inside the Main component header I have a form that should redirect the user to /profile. The only way that I was able to get this working is to add && this.props.location.pathname !== '/profile' to the state.toProfile condition. This feels a bit dirty. It seems like there is a better way.
I am taking the approach recommended in this blog post https://tylermcginnis.com/react-router-programmatically-navigate/, it seems like this doesn't work if the route you redirect to contains the same component as the route that was redirected from.
class App extends Component {
render() {
return (
<BrowserRouter>
<Switch>
<Route path="/dashboard" component={Dashboard} />
<Route component={Main} />
</Switch>
</BrowserRouter>
);
}
}
class Main extends Component {
state = {
toProfile: false
}
render() {
if (this.state.toProfile === true) {
return <Redirect to='/profile' />
}
return (
<header>
...
</header>
);
}
}
you need to add a route for /profile, atm profile goes to Main that goes to /profile... again
I have a component that boots up a timer with setInterval in the componentDidMount in my component associated with /testRoute.
That looks like:
componentDidMount() {
if (this.timer == null) {
var timerFunction = function() { this.tick() }.bind(this)
this.timer = setInterval(timerFunction, 500);
}
}
I have a balancing line of code to remove the timer when the component 'goes away' (not sure to what extent this lifecycle hook is equivalent to the object dying, e.g. getting deallocated),
componentWillUnmount() {
clearInterval(this.timer)
}
The problem is that if I'm on route /testRoute and I refresh the browser, constructor() is called twice, and so is componentDidMount. At this point, there are two "timers" ticking. If I navigate to another route, componentWillUnmount is called and one of the timers is cleared. But that still leaves the one.
Is it expected behavior that my component should hit componentDidMount twice on page refresh? And if so, how can I ensure that only one timer is ever setup?
edit:
-demonstrating this with a skeleton example:
function Nav() {
return (
<ul className='nav'>
<li><Link to='/test'>test</Link></li>
</ul>
)
}
With a single route:
class App extends React.Component {
render() {
return (
<Router>
<div>
<Nav />
<Route path='/test' component={Test} />
</div>
</Router>
)
}
}
And a trivial component:
class Test extends React.Component {
constructor() {
super()
console.log("constructor")
}
render() {
return <div>test</div>
}
}
In this code: "constructor" is logged to the console twice on refreshing /test.
I have similar issue which my component mounted twice. In my case, I make a request in componentDidMount which called twice. I render some of Route by taking from server by async call (App.ROUTES), and some as static (routeInfo.insideDashboard). And the static components mount twice.
<Dashboard specialMenu={this.state.menu}>
<Switch>
{routeInfo.insideDashboard.map(route =>
<Route exact key={route.path} path={route.path} component={Auth(route.component)}/>)}
{App.ROUTES.map(route => <Route key={route.urlPath} exact path={"/" + route.urlPath}
component={Auth(asyncComponent(route.importPath, {title: route.title}))}/>)}
<Route component={NotFound}/>
</Switch>
</Dashboard>
I suspect that the reason can be render of static routes (routeInfo.insideDashboard) twice, before other routes come from server and after it. I add a check routesFromServerTaken before render Routes which ensures Route (definitions) render only once.
<Dashboard specialMenu={this.state.menu}>
<Switch>
{routesFromServerTaken ? routeInfo.insideDashboard.map(route =>
<Route exact key={route.path} path={route.path} component={Auth(route.component)}/>): null}
{App.ROUTES.map(route => <Route key={route.urlPath} exact path={"/" + route.urlPath}
component={Auth(asyncComponent(route.importPath, {title: route.title}))}/>)}
<Route component={NotFound}/>
</Switch>
</Dashboard>
This solved my problem, components mount only once now.
I'm building an article search with React [15.6.1] and Router [4.1.1] and noticed that when trying to access an article directly the previous component is loaded, even thou it's not the one that's being called
// === Layout ==================
class Layout extends React.Component {
render() {
return (
<Provider store={this.props.store}>
<HashRouter>
<div>
<Switch>
<Route exact path="/" component={SearchFilter} />
<Route path="/article/:guid" component={Article} />
</Switch>
</div>
</HashRouter>
</Provider>
);
}
}
// === SearchFilter ==================
class SearchFilter extends React.Component {
componentDidMount() {
console.log('SearchFilter Did Mount');
}
render() {
return (
<div>...</div>
);
}
}
// === Article ==================
class Article extends React.Component {
componentDidMount() {
console.log('Article Did Mount');
}
render() {
return (
<div>...</div>
);
}
}
So when going to the root localhost:3000/#/ it prints
// SearchFilter Did Mount
And when I access an article directly like this localhost:3000/#/article/123456 it prints
// SearchFilter Did Mount
// Article Did Mount
So my question is, how can I prevent it from running the previous route?
Because I would like to dispatch some actions there that would trigger some ajax calls to the webservice.
Thanks
Try this instead :
<HashRouter basename="/">
<div>
<Switch>
<Route exact path="/search" component={SearchFilter} />
<Route path="/article/:guid" component={Article} />
</Switch>
</div>
</HashRouter>
Edit:
For me its working well... So like said... there is something really weird and hidden happening on your machine, or you just put / and then rewrite url to /#/article/123 and it cause the first log stays in the console, but its from the previsous url "/" and if you reload the browser by F5 on the new url /#/article/123 you will see only the "Article did mount"
I am currently using "react-router": "^2.4.0" on my chat application and confused on how to redirect when there is no user. I don't think redirect works in "^2.4.0" .
Should I use onEnter hooks on my chat path?
Something like this:
<Route path="chat" component={Chat} onEnter={Chat.willTransitionTo}/>
routes.js
<Route path="/" component={App} >
<IndexRoute component={Chat} />
<Route path="chat" component={Chat} />
<Route path="login" component={Login} />
</Route>
Chat.jsx
static willTransitionTo(transition){
var state = ChatStore.getState();
if(!state.user){
transition.redirect('/login');
}
}
I made it work by using setRouteLeaveHook as it says here:
https://github.com/ReactTraining/react-router/blob/master/docs/guides/ConfirmingNavigation.md
return false to prevent a transition w/o prompting the user
However, they forget to mention that the hook should be unregistered as well, see here and here.
We can use this to implement our own solution as follows:
class YourComponent extends Component {
constructor() {
super();
const {route} = this.props;
const {router} = this.context;
this.unregisterLeaveHook = router.setRouteLeaveHook(
route,
this.routerWillLeave.bind(this)
);
}
componentWillUnmount() {
this.unregisterLeaveHook();
}
routerWillLeave() {
// return false to prevent a transition w/o prompting the user,
// or return a string to allow the user to decide:
if (!this.state.isSaved) {
return 'Your work is not saved! Are you sure you want to leave?'
}
}
...
}
YourComponent.contextTypes = {
router: routerShape
};
There is at least a spelling error:
tansition.redirect('/login');
Should be:
transition.redirect('/login');
You should also check the auth-flow example of react-router: https://github.com/reactjs/react-router/tree/master/examples/auth-flow