Split my routes into two (for now, going to be four later) separate components.
const RouterComponentOne = () => {
return (
<Switch>
<Route path="/routeA" component={ComponentA} />
<Route path="/routeB" component={ComponentB} />
</Switch>
)
}
const RouterComponentTwo = () => {
return (
<Switch>
<Route path="/routeC" component={ComponentA} />
<Route component={Component404} />
</Switch>
)
}
const RouterDefault = () => {
return (
<>
<RouterComponentOne />
<RouterComponentTwo />
</>
)
}
export default RouterDefault;
When visiting any route from RouterComponentOne the 404 component is rendered (which it shouldn't). It renders on non-existent routes (example /test/123/abc which is correct, and doesn't render on any RouterComponentTwo route.
I added some dummy components to the RouterDefault and the 404 component rendered on all additional paths as well.
In short, the 404 route isn't matched/rendered within the routes of the component where the 404Route is. If put into the RouterComponentOne it won't render on routeA and routeB but will render for routeC
How can I solve this?
You need a property called exact
const RouterComponentOne = () => {
return (
<Switch>
<Route path="/routeA" exact component={ComponentA} />
<Route path="/routeB" exact component={ComponentB} />
</Switch>
)
}
Related
I'm practicing nested routing using react-router-dom.
In Room001Container, there is a tab navigation bar that will be displayed on all components, and there is a button that navigates to a detailed post page when the button is pressed.
When I enter the room, I should see the post page first by default.
However, if I output postId as useParams in Room001Container component, undefined is returned.
In the PostDetail component, params are printed well, but why is it not output in the Room001Container? I know that the container is unconditionally rendered even if I move to each component, but it's really strange. Is there any way to know the params from the top container?
function Container() {
return (
<Switch>
<Route
path="/channel/:channelId/lobby/:lobbyId"
component={LobbyContainer}
/>
</Switch>
);
}
function LobbyContainer() {
return (
<Switch>
<Route
exact
path="/channel/:channelId/lobby/001"
component={Room001Container}
/>
<Route
exact
path="/channel/:channelId/lobby/002"
component={Room002Container}
/>
<Route
exact
path="/channel/:channelId/lobby/002"
component={Room003Container}
/>
</Switch>
);
}
function Room001Container() {
const history = useHistory();
const { pathname } = history.location;
const { channelId, postId } = useParams();
console.log(postId) // undefined
return (
<>
<S_TabBar>
<Link to="/channel/:channelId/lobby/001/post">
<S_TabCell>post</S_TabCell>
</Link>
<Link to="/channel/:channelId/lobby/001/chat">
<S_TabCell>chat</S_TabCell>
</Link>
</S_TabBar>
<S_Button onClick={() => history.push('/channel/:channelId/lobby/001/post/1')}>
go to detail
</S_Button>
<Switch>
<Route
exact
path="/channel/:channelId/lobby/001/post"
component={Post}
/>
<Route
exact
path="/channel/:channelId/lobby/001/post/:postId"
component={PostDetail}
/>
<Route
exact
path="/channel/:channelId/lobby/001/chat"
component={Chat}
/>
<Redirect
exact
from="/channel/:channelId/lobby/001"
to="/channel/:channelId/lobby/001/post"
/>
</Switch>
</>
);
}
There is no postId parameter in the route that mounts Room001Container.
You'll want e. g.
<Route
exact
path="/channel/:channelId/lobby/:postId"
component={Room001Container}
/>
I have a routing setup where if only 1 param is given, i.e /:id I want the router to always redirect to /:id/overview.
For example, if the user goes to /hello, I want them to be redirected to /hello/overview.
I've tried doing this like this:
<Switch>
<Route exact path="/" component={NoParam} />
<Redirect from="/:section" to="/:section/overview" />
<Route exact path="/:section/:pageName" component={GeneralOverviewPage} />
</Switch>
This causes an infinite re-render. I'm not sure how to achieve this redirect, and would really appreciate any help. Thanks.
EDIT=======
Now trying to do it like this:
const GeneralOverviewPage: FC<RouteComponentProps<GeneralOverviewPageProps>> = (
props
) => {
console.log(props);
return !props.match.params.pageName ? (
<Redirect to={props.match.params.section + '/overview'} />
) : (
<h1>{props.match.params.pageName}</h1>
);
};
export default GeneralOverviewPage;
and
<Route path="/:section" component={GeneralOverviewPage} />
<Route path="/:section/:pageName" component={GeneralOverviewPage} />
This means that /hello is now redirecting to /hello/hello/overview....
This will Help You around for understanding!
import React from 'react';
import { Switch, Route, Redirect } from 'react-router-dom'
const App = () => {
return (
<Switch>
<Route exact path="/" render={
() => console.log("Hi")
} />
<Redirect exact from="/:section" to="/:section/overview" render={
() => console.log("Hi 1")
} />
<Route exact path="/:section/:pageName" render={
() => console.log("Hi 2")
} />
</Switch>
)
}
export default App;
Your <Redirect from="/:section" to="/:section/overview" /> must be inside GeneralOverviewPage component.
if(!pageName){
return (<Redirect from="/:section" to="/:section/overview" />);
}
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'm trying to do layouts with react-router.
When my user hits / I want to render some layout. When my user hits /login, or /sign_up I want the layout to render, with the relevant component for /login or /sign_up rendered.
Currently, my App.js looks like this
return (
<div className={className}>
<Route path="/" component={Auth} />
<ModalContainer />
</div>
);
My Auth.js looks like this
return (
<AuthFrame footerText={footerText} footerClick={footerClick}>
<Route path="/login" component={LoginContainer} />
<Route path="/sign_up" component={SignUpContainer} />
</AuthFrame>
);
So AuthFrame will get rendered when I hit /, and then react router looks for login or sign_up to render the other containers.
However, when I hit /, only the AuthFrame will render.
I would like for / to be treated as /login.
How do I achieve this?
The Switch component is useful in these cases:
return (
<AuthFrame footerText={footerText} footerClick={footerClick}>
<Switch>
<Route path="/login" component={LoginContainer} />
<Route path="/sign_up" component={SignUpContainer} />
{/* Default route in case none within `Switch` were matched so far */}
<Route component={LoginContainer} />
</Switch>
</AuthFrame>
);
see: https://github.com/ReactTraining/react-router/blob/master/packages/react-router/docs/api/Switch.md
I think you're forced to introduce a prop/state which indicates the status of your viewer. This means is he signed in or just a guest of your website.
Your router can't obviously render /login if you you hit / but the router allows you to redirect to another page:
class AuthContainer extends React.Component {
defaultProps = {
loggedIn: false
}
render() {
return <div>
<Route path="/login" component={LoginContainer}/>
<Route path="/sign_up" component={SignUpContainer}/>
</div>
}
}
class PublicHomePage extends React.Component {
render() {
return <div>
<Route path="/settings" component={SettingsComponent}/>
<Route path="/profile" component={ProfileComponent}/>
<Route path="/and_so_on" component={AndSoOnComponent}/>
</div>
}
}
class App
extends React.Component {
defaultProps = {
loggedIn: false
}
render() {
const {loggedIn} = this.props;
if (loggedIn) {
return <PublicHomePage/>
}
return <Route exact path="/" render={() => (
<Redirect to="/login"/>
)}/>
}
}
I hope this code works for you. It isn't quite perfect but it should give you an idea how you could solve your problem.
In your case I would probably manipulate a bit with Routes in react-router. This code in AuthFrame should do the trick:
return (
<AuthFrame footerText={footerText} footerClick={footerClick}>
{["/", "/login"].map((path, ind) =>
<Route exact key={ind} path={path} component={LoginContainer} />
)}
<Route exact path="/sign_up" component={SignUpContainer} />
</AuthFrame>);
Note the usage of exact on the routes, this is to prevent matching login component on /sign_up since it will also match / and prevent rendering both login and signup when accessing the root path (/).
I don't really get how to constrain params with, for example a regex.
How to differentiate these two routes?
<Router>
<Route path="/:alpha_index" component={Child1} />
<Route path="/:numeric_index" component={Child2} />
</Router>
And prevent "/123" from firing the first route?
React-router v4 now allows you to use regexes to match params -- https://reacttraining.com/react-router/web/api/Route/path-string
const NumberRoute = () => <div>Number Route</div>;
const StringRoute = () => <div>String Route</div>;
<Router>
<Switch>
<Route exact path="/foo/:id(\\d+)" component={NumberRoute}/>
<Route exact path="/foo/:path(\\w+)" component={StringRoute}/>
</Switch>
</Router>
More info:
https://github.com/pillarjs/path-to-regexp/tree/v1.7.0#custom-match-parameters
I'm not sure if this is possible with React router at the moment. However there's a simple solution to your problem. Just do the int/alpha check in another component, like this:
<Router>
<Route path="/:index" component={Child0} />
</Router>
const Child0 = (props) => {
let n = props.params.index;
if (!isNumeric(n)) {
return <Child1 />;
} else {
return <Child2 />;
}
}
* Note that the code above does not run, it's just there to show what I mean.