I just took over a React project from one of my colleague but I cannot understand the logic in the code below.
content = <Switch>
<Route path="/login" exact component={LoginPage} />
<Route render={() => { return <Redirect to="/login" />; }} />
</Switch>
I know how to use Route with Component, with Render, but Render with Redirect, first time I saw it.
Thanks
This appears to be just another way of just saying:
<Redirect path='*' to='/login' />
Since it is inside a <Switch>, and after any <Route>, it will always match (if nothing above it got matched) and get rendered.
When the Redirect component gets rendered, it does its job of redirecting to the page specified in the to prop.
I found that out by doing some reading of the source code. If you're interested, there's a bit of indirection, but basically the Redirect component renders a Lifecycle component which will call method with the location provided as soon as it's mounted.
method is set like this:
const method = push ? history.push : history.replace;
And that's done like that because apparently the <Redirect> component can take push as a boolean prop, to set the behaviour of how the redirect is actually achieved.
Redirect component source https://github.com/ReactTraining/react-router/blob/master/packages/react-router/modules/Redirect.js
Lifecycle component source: https://github.com/ReactTraining/react-router/blob/master/packages/react-router/modules/Lifecycle.js
Related
In my render function I have
<Route path="/classes/:course" render={(match) => (
<Redirect to={`/classes/${match.params.course}/home`} />
)}>
<Route path="/home" component={Home} />
</Route>
For example if the param for "course" was "BIO1001", I want to redirect the page to "/classes/BIO1001/home" when I go to the page "/classes/BIO1001/". Previously I tried simply putting a Redirect tag with "from" and "to" but ran into the problem of the url actually going to "/classes/:course/home" instead of "/classes/BIO1001/home"
Also would the nested route with path="/home" go to "/classes/BIO1001/home"? I was unsure on how I can set a route where the path starts from the previous url (in this case starting from "/classes/:course/"
The first problem is right here:
render={(match) => ( ...
The render function gets a props object which contains a match property. Instead of destructuring the match property, what you are actually doing is assigning the whole props object to a variable match. So when you go to access match.params it won't be found.
You need curly braces around match in order to destructure it.
render={({match}) => ( ...
The second problem is the nesting of the two Route components. I get a warning:
Warning: You should not use <Route render> and <Route children> in the same route; <Route render> will be ignored
So based on that warning you can see that your Redirect is being entirely ignored since it comes from render. The child Route is seen as the render function for the classes Route.
I'm assuming you have various subpages of a course? And we want to force the URL to include "/home" if none is set? (Personally I would do the opposite and redirect "/home" to the course root URL).
Previously I tried simply putting a Redirect tag with "from" and "to" but ran into the problem of the url actually going to "/classes/:course/home" instead of "/classes/BIO1001/home"
Per the docs, you can use params in your Redirect, but only if it is inside a Switch.
Here's a sample code to do that:
const CoursePage = () => {
// you can access arguments from the props or through hooks
const { course, tab } = useParams();
// not sure how you want to handle the different tabs
return <div>Viewing Course {course}</div>;
};
const App = () => (
<BrowserRouter>
<Switch>
<Route path="/classes/:course/:tab"><CoursePage/></Route>
<Redirect from="/classes/:course" to="/classes/:course/home"/>
</Switch>
</BrowserRouter>
);
export default App;
Your nested routing is true i think. But you are rendering your Home component without any dynamic props. Try it like below:
<Route path="/classes/:course" render={(match) => (
<Redirect to={`/classes/${match.params.course}/home`} />
)}>
<Route path="/home" >
<Home someProps={someValue} />
</Route>
</Route>
Note: The Redirect element can be used without a containing Route element by providing a from property. In this case, you can just use the URL parameter tokens in both from and to, and they'll be carried over for you. For example, if you're using a Switch block...
<Switch>
{/* various app routes... */}
{/* redirect action */}
<Redirect from="/classes/:course" to="/classes/:course/home" />
<Switch>
I'm having a strange issue with React Router. My PrivateRoute wrapper doesn't do its thing when navigating using a Link or NavLink.
Essentially I have something like this:
<BrowserRouter>
<Switch>
<Route exact path="/"><Home /></Route>
<PrivateRoute exact path="/private"><Private /></PrivateRoute>
</Switch>
</BrowserRouter>
PrivateRoute is just a wrapper around Route that checks for authentication.
Now, if I go to /private by typing it in the address bar, PrivateRoute does its job (and redirects to /login, but that doesn't matter).
However, if I use a NavLink in the Home component which has to="/private", React Router routes to the private route even if the user is unauthenticated.
Is there any way I can resolve this reasonably? And why does React Router behave like this and doesn't "go through" all the routes in BrowserRouter each time you navigate using a NavLink?
To solve your problem, you need to add exact or exact="true" on public Route.
If you don't include exact="true", <Route path="/"> refers all sub routes starting with /.
So /private will match / route hence it will render <Home /> component.
Here is updated code
<BrowserRouter>
<Switch>
<Route path="/" exact>
<Home />
</Route>
<PrivateRoute path="/private">
<Private />
</PrivateRoute>
</Switch>
</BrowserRouter>
Here is sample code
Well, this was a quite simple and dumb fix actually.
The problem was that the PrivateRoute component didn't "remount" when changing routes using Links (that is interesting and somehow clever behavior of React Router, I can imagine this improves performance).
Meanwhile, typing a URL into the address bar refreshes the entire page, thus "remounting" all components.
Since the logic for checking auth was in componentDidMount only, it didn't launch if the component wasn't "remounted", therefore creating my issue.
For any possible future visitors, to fix this problem, just move your auth checking logic from componentDidMount somewhere else, and reference it both in componentDidMount and componentDidUpdate. Don't forget to check if props actually changed in componentDidUpdate — otherwise you'll create an infinite loop — like so:
componentDidUpdate(prevProps, prevState) {
// You could also use shallowCompare, but that wasn't needed in my case
if (this.props.requiredPerms !== prevProps.requiredPerms) {
this.checkAuth(); // method with the logic which was in componentDidMount before
}
}
I have this route that I want to have some stuff executing when the user leaves it.
So I found the onLeave hook
<Switch>
<Route exact path="/layout" onLeave={ console.log("LEFT LAYOUT") } render={(props) => <GridLayoutApps retracted={this.state.sideBarRetracted} {...props}/>} />
</Switch>
Its supposed to console.log "Left layout" whenever I'm in /layout, and decide to load another route. Point is, this doesn't seem to happen, since the console.log is loading each time I switch ANY route.
I have the routes in my app.js file, which I
export default withRouter(App);
I have encapsulated it like this in index
ReactDOM.render(<BrowserRouter><Provider store={store}><App /></Provider></BrowserRouter>, document.getElementById('root'));
onLeave needs to be a function. At present its just an normal prop
<Switch>
<Route exact path="/layout" onLeave={() => {console.log("LEFT LAYOUT")} } render={(props) => <GridLayoutApps retracted={this.state.sideBarRetracted} {...props}/>} />
</Switch>
However please note that onLeave is not longer available in react-router v4 onwards and you need to use the componentWillUnmount or the useEffect hook to trigger the same behaviour
I have something like this:
<Route path="/route/:param" component={DealContainer} />
Then while the component is mounted I am doing a client side redirect:
componentWillMount() {
if (this.props.match.params.param != 'desired_one') {
this.props.history.push('/route/desired_one');
Despite the fact that the url changes the component is not remounted...
Any ideas?
You should resolve this issue by using the Redirect component inside "react-router-dom" package
<Route exact path="/route" component={DealContainer} />
<Route
exact
path="/route/desired"
render={() => <Redirect to="/route/desiredRedirectLocation" />}
/>
<Route path="/route/:param" component={DealContainer} />
This implementation should:
Match the exact base route correctly, without matching any props. Lets say that you want to show a list of items (e.g. /products)
Match the desired item to be redirected (e.g. products/car) and redirect it to the desired location, let's say products/horse
Match any other case that you don't want to redirect /products/:id and correctly take the prop you are after inside the match object.
Explanation
The problem with history.push, is that React will figure out you are using the same component, and will only update the differences, without actually re-mounting the component, which is already mounted.
My example with redirect on the other hand, will intercept the route you want to redirect without mounting the component first, so component will be mounted after the redirect happened, correctly executing the action that you need.
I have a React MaterialUI AppBarcomponent with property title , that I am changing based on the value returned by window.location.pathname. So as the page/url changes, the title will change with it. Looks something like below:
<AppBar
title={this.renderTitle()}
/>
renderTitle() {
if (window.location.pathname === '/home'
return 'home';
} else if (window.location.pathname === '/login'
return 'login';
}
The issue I am running into is that renderTitle() does not get executed if a different component (so not the AppBar) causes the page/url change.
E.g. another separate React component on the page triggers the page to change, which I'd hoped with trigger renderTitle(), but it doesn't... thus the title property never updates. So if I am navigating from /home to /login, the following will happen:
pathname is /home
user presses a button which runs a function, submit(), which is used to change the page w/ react-router
renderTitle() is run at this point, but window.location.pathname is still returning the previous page
submit() changes the page to /login
window.location.pathname is now correctly set to /login, but it is too late as renderTitle() has already been run
any help is appreciated, thanks
The best way is to use react-document-title library.
From documentation:
react-document-title provides a declarative way to specify document.title in a single-page app.
This component can be used on server side as well.
If your component that renders AppBar is an actual route you can just read the pathname from the props that react router injects.
<Router>
<Route path="/" component={App}>
<Route path="about" component={About} />
<Route path="inbox" component={Inbox}>
<Route path="messages/:id" component={Message} />
</Route>
</Route>
</Router>
For example in App, About, Inbox and Message you have access to the router props. And you can also pass the props to their children.
render() {
return (
<AppBar
title={this.renderTitle(this.props.location.pathname)}
/>
);
}
And in your function just use the parameter to return the correct result. Now because you are using props your component will update automatically when they change.