I have 2 routes which render the same component.
Once I am on route path "/route1", I click on a link which routes me to /route2. I expect componentWillUnmount() to be called but instead the render method is called again with prop type "type2". Why is this happening?
I have already tried adding a key to each Route but it does not work.
`<Switch>
<Route
path={'/route1'}
render={props => (
<Component1
type={type1}
{...props}
/>
)}
/>
<Route
path={'/route2'}
render={props => (
<Component1
type={type2}
{...props}
/>
)}
/>
</Switch>`
Related
I want to implement the PrivateRoute HOC which will check if the user is logged in then render the corresponding component, otherwise redirect to the login page.
Here is the PrivateRoute HOC:
import React from 'react';
import { Route, Redirect } from 'react-router-dom';
const PrivateRoute = ({ component, ...rest }) => (
<Route {...rest} render={(props) => (
sessionStorage.getItem('userToken') ? (
React.createElement(component, props)
) : (
<Redirect to={{
pathname: '/login',
}} />
)
)} />
);
export default PrivateRoute;
Here's how I'm using it:
import PrivateRoute from './components/HOC/PrivateRoute';
...
render() {
if (this.props.hasError) return <h1>Sorry, something went wrong...</h1>;
if (this.props.isLoading) return <h1>Loading...</h1>;
return (
<div className="App">
<Switch>
<PrivateRoute exact path="/" component={<Home /> } />
<Route exact path="/login" render={props => <Login {...props} />} />
<Route exact path="/registration" render={props => <Registration {...props} />} />
<PrivateRoute exact path="/account" component={<Account />} />
</Switch>
</div>
);
}
But it throws an error:
I find a GitHub thread where I find a possible answer for why it happens like that, but I don't know how to implement that answer for my case:
I tried some experimental ways to achieve that but everything fails.
React.createElement accepts a type or a React component before it invoked.
React.createElement(
type,
[props],
[...children]
)
The type argument can be either a tag name string (such as 'div' or 'span'), a React component type (a class or a function), or a React fragment type.
const Component = () => <>Hello</>;
// Good
React.createElement(Component)
React.createElement("div")
// Bad
React.createElement(<div/>)
In your case you use the "bad" version.
You should use cloneElement or fix it with a wrapper:
// Good
React.cloneElement(<Component />)
// Or make a wrapper
React.createElement(() => <Component />));
So finally:
// Change to clone
React.cloneElement(component, props)
// **OR** make a wrapper
<PrivateRoute exact path="/" component={() => <Home />} />
In my React app, I can see the following code;
const pages = [
{
pageLink: '/state/:stateCode',
view: State,
displayName: t('State'),
animationDelayForNavbar: 0.7,
showInNavbar: false,
},
];
<Route render={({location}) => (
<div className="Almighty-Router">
<Navbar
pages={pages}
darkMode={darkMode}
setDarkMode={setDarkMode}
/>
<Switch location={location}>
{pages.map((page, index) => {
return (
<Route
exact
path={page.pageLink}
render={({match}) => (
<page.view key={match.params.stateCode || index} />
)}
key={index}
/>
);
})}
<Redirect to="/" />
</Switch>
</div>
)}
/>
My question is why is there a nested <Route> used below ?
What purpose does it serve? Can we somehow implement this without nested <Route> element ?
It's because your component might have been developed sometime back.
The recommended method of rendering something with a <Route> is to use children elements. There are, however, a few other methods you can use to render something with a <Route>. These are provided mostly for supporting apps that were built with earlier versions of the router before hooks were introduced.
Instead of having a new React element created for you using the component prop, you can pass in a function to be called when the location matches. The render prop function has access to all the same route props (match, location and history) as the component render prop.
You do not need to render a nested route, specially because your Parent route is not defining any path.
You can directly render he above component like below
<div className="Almighty-Router">
<Navbar
pages={pages}
darkMode={darkMode}
setDarkMode={setDarkMode}
/>
<Switch>
{pages.map((page, index) => {
return (
<Route
exact
path={page.pageLink}
render={({match}) => (
<page.view key={match.params.stateCode || index} />
)}
key={index}
/>
);
})}
<Redirect to="/" />
</Switch>
</div>
Also the Switch component doesn't need a location prop
In my app, I'd like to match all routs that end with #something.
/map#login
/info#login
and
/map#register
/map/one#register
/info#register
/info/two#register
So I can show component as popup on top of the content. How this can be done?
I found a solution for this case. It was inspired from this question in stackOverflow. Using HashRoute wrapper for Route and showing component based on location.hash.
const HashRoute = ({ component: Component, hash, ...routeProps }) => (
<Route
{...routeProps}
component={({ location, ...props }) =>
location.hash === hash && <Component {...props} />
}
/>
);
export default class App extends Component {
constructor() {
super();
}
render() {
return (
<div className='App'>
<Router history={history}>
<HashRoute hash='#login'component={Login} />
<HashRoute hash='#register' component={Register} />
<Switch>
<Route exact path='/map' component={Map} />
<Route exact path='/info' component={Info} />
</Switch>
</Router>
</div>
);
}
}
Updating/improving from the other answer here. It would better to not use the component prop as it won't create new instance of the routed component each time the Route is rendered for any reason. The custom HashRoute component should return valid JSX, either a Route component or null.
Example:
const HashRoute = ({ hash, ...routeProps }) => {
const location = useLocation();
return location.hash === hash
? <Route {...routeProps} />
: null
};
...
<Router>
<HashRoute hash='#login' component={Login} />
<HashRoute
hash='#register'
render={props => <Register {...props} otherProp />}
/>
<HashRoute hash='#something'>
<Register otherProp />
</HashRoute>
<Switch>
<Route path='/map' component={Map} />
<Route path='/info' component={Info} />
</Switch>
</Router>
I ask this question just want to make sure that I understand the dynamic nested routes in React Router v4 correctly. I want to build a hacker news client similar to this one. But I am bogged down by setting up the dynamic routes.
In React Router v4, if I follow other tutorials on the web using the match object I will have something like this (A super simple one):
const ChildComponent = ({rows, match}) => (
<div>
<ol>
rows.map((row, i) => {
return (
<li key={row.id}>
<a href={row.url}>row.title</a> // topic title
<Link to=`${match.url}/${row.by}`>row.by</Link> // topic author
</li>
)
}
</ol>
<Route path=`${match.url}/:userId` render={(props) => <UserProfile fetchUserData={fetchUserData} {...props} />} />
</div>
)};
And when we render the parent component, we usually use something like this for routing:
<Switch>
<Route path="/" render={Home} />
<Route path="/topics" render={(props) => <ChildComponent rows={rows} {...props} /> } />
<Route path="*" render={() => <div>Not Found</div>} />
</Switch>
But this is not ideal for this case, as when I click to view the author's info I need to display a url like this: "http://mysite/user/userid" instead of the current one which is "http://mysite/news/userid".
However, if I change ${match.url}/${row.by} to /user/${row.by} and change ${match.url}/:userId to /user/:userId the route is not recognized in the app. The route begins with /user/ is simply skipped, it will go straight to the app's NotFound route (if there is one), which is in the parent component. Why will the links in child component try to match the routes in the parent if I don't use ${match.url} in the route?
I have added a demo for you to easier to understand this problem.
Because when you'll click the /user/:userId: link the app will parse the Router's Switch to see if something matches. If it doesn't it fallback to *.
In your case, you did not specify anything in the Switch to handle /user .
You'll need to move your userId Route declaration to the Switch as they won't share the same first route (/user !== /topics).
<Switch>
<Route path="/" render={Home} />
<Route path="/topics" render={(props) => <ChildComponent rows={rows} {...props} /> } />
<Route path="/user/:userId" render={(props) => <UserProfile fetchUserData={fetchUserData} {...props} />} />
<Route render={() => <div>Not Found</div>} />
</Switch>
I am using react-router v4 along side react-loadable to async load matched routes.
<LayoutWrapper>
<Route
exact
path="/"
render={props =>
currentUser ? (
<AsycnExamplePage {...props} currentUser={currentUser} />
) : (
<AsycnExamplePage />
)}
/>
<Route
exact
path="/saved"
render={props => <AsycnExamplePage {...props} currentUser={currentUser} />}
/>
<Route
exact
path="/settings"
render={props => (
<AsycnExamplePage {...props} currentUser={currentUser} />
)}
/>
</LayoutWrapper>
AsycnExamplePage is a component wrapped with Loadable from react-loadable, e.g:
Loadable({
loader: () => import("./index"),
loading: props => {
if (props.isLoading) {
NProgress.start();
}
return null;
},
});
Currently, as I route to a different page, react-router will instantly match the route before the async route is resolve. Thereby showing the LoadingComponent from react-loadable.
How can I make it so that react-router will only render the async component after the async component is resolved?
PS: I'm trying to create a page load effect similar to Youtube.