Nesting React routes to login-protected pages [duplicate] - react-router

This question already has answers here:
How to create a protected route with react-router-dom?
(5 answers)
Closed 8 months ago.
I am using react-router-dom#6.3.0
I have created a React app where certain Private pages are accessible only users who have logged in.
You can find a demo here, and a GitHub repo here.
A simplified version of this is shown below.
I have wrapped every Private page in its own RequireLogin component, and this works:
<Route
path="/private1"
element={
<RequireLogin redirectTo="/">
<Private
text="Private Page #1"
/>
</RequireLogin >
}
/>
The RequireLogin component redirects to a page with the Login component if the user is not logged in, and renders the requested component only to a logged in user.
My question is this:
Is it there a way to wrap all the Private routes inside one RequireLogin component, or do I have to wrap each one separately?
import React, { createContext, useContext, useState } from 'react'
import ReactDOM from 'react-dom';
import {
BrowserRouter as Router,
Routes,
Route,
Navigate,
NavLink
} from "react-router-dom";
const UserContext = createContext()
const UserProvider = ({children}) => {
const [ loggedIn, logIn ] = useState("")
return (
<UserContext.Provider
value={{
loggedIn,
logIn
}}
>
{children}
</UserContext.Provider>
)
}
function App() {
return (
<Router>
<UserProvider>
<Routes>
<Route
path="/"
element={<NavLink to="/login">Log In</NavLink>}
/>
<Route
path="/login"
element={<Login />}
/>
<Route
path="/private1"
element={
<RequireLogin redirectTo="/login">
<Private
text="Private Page #1"
/>
</RequireLogin >
}
/>
<Route
path="/private2"
element={
<RequireLogin redirectTo="/login">
<Private
text="Private Page #2"
/>
</RequireLogin >
}
/>
</Routes>
</UserProvider>
</Router>
);
}
function Menu({hideLogOut}) {
const { loggedIn } = useContext(UserContext)
if (loggedIn) {
if (!hideLogOut) {
return <ul>
<li><NavLink to="/login">Log Out</NavLink></li>
<li><NavLink to="/private1">Private #1</NavLink></li>
<li><NavLink to="/private2">Private #2</NavLink></li>
</ul>
} else {
return <ul>
<li><NavLink to="/private1">Private #1</NavLink></li>
<li><NavLink to="/private2">Private #2</NavLink></li>
</ul>
}
} else {
return <p>Not Logged In</p>
}
}
function RequireLogin ({ children, redirectTo }) {
const { loggedIn } = useContext(UserContext);
return loggedIn
? children
: <Navigate to={redirectTo} />;
}
function Private({text}) {
return (
<div>
<Menu />
<h1>{text}</h1>
</div>
)
}
function Login() {
const { loggedIn, logIn } = useContext(UserContext)
const toggleLogged = () => {
logIn(!loggedIn)
}
return (<div>
<Menu
hideLogOut={true}
/>
<label htmlFor="loggedIn">
<input
type="checkbox"
name="loggedIn"
id="loggedIn"
checked={loggedIn}
onChange={toggleLogged}
/>
Pretend that we are logged in
</label>
</div>)
}
ReactDOM.render(
<App />,
document.getElementById('root')
);

I use a second router for the private routes, wrapped with a single <RequireLogin>. Example:
<Routes>
<Route path="/login" element={<LoginPage />} />
<Route path="/register" element={<RegistrationPage />} />
<Route path="*" element={
<RequireLogin>
<Routes>
<Route path="/" element={<FeedPage />} />
<Route path="/explore" element={<ExplorePage />} />
<Route path="/user/:username" element={<UserPage />} />
<Route path="*" element={<Navigate to="/" />} />
</Routes>
</RequireLogin>
} />
</Routes>

Related

react Browser URL changed but useLocation hook doesn't get update

I am creating a dashboard using nested routes. But when I tries clicking a Link, the URL on the browser does change but the useLocation hook doesn't get update so my useEffect hook doesn't get fired to reload data.
This is my App main routes:
function App() {
return (
<Router>
<Header />
<Switch>
<Route path="/login" component={ Login } />
<ProrectedRoute exact path="/" component={ Home } />
<ProrectedRoute path="/blogs/:title" component={ BlogPage } />
<ProrectedRoute path="/new" component={ NewBlog } />
<ProrectedRoute path="/dashboard/:part" component={ UserDashboard } />
<ProrectedRoute path="/:name" component={ ProfilePage } />
</Switch>
<Footer />
</Router>
);
}
And this is my dashboard:
export const UserDashboard = () => {
const dispatch = useDispatch();
const location = useLocation();
useEffect(() => {
switch(location) {
case '/dashboard/posts':
dispatch(getOwnPosts());
break;
case '/dashboard/bookmarks':
dispatch(getBookmarkPosts());
break;
case '/dashboard/followings':
break;
default:
break;
};
}, [location]);
return (
<div className="dashboard">
<Router>
<div className="dashboard__sidebar">
<Link to="/dashboard/posts">Your posts</Link>
<Link to="/dashboard/bookmarks">Your bookmarks</Link>
<Link to="/dashboard/followings">Your followings</Link>
</div>
<div className="dashboard__main">
<Switch>
<Route exact path="/dashboard/posts">
<BlogRecommendationList selector={selectUserPosts} />
</Route>
<Route exact path="/dashboard/bookmarks">
<BlogRecommendationList selector={selectUserBookmarkedPosts} />
</Route>
</Switch>
</div>
</Router>
</div>
)
}
I have found the error. It is because I use 2 Router in the same application (1 in the App.js and 1 in Dashboard.js). There should be only 1 router in an application. Remove Router from dashboard makes it work.

React Router Dom v5 nested routes don't work

Been trying to figure out why the nested routes don't work in my child component. What am I missing here? Below is the component that fails to display anything when trying both routes "/" and "/add-new-strategy".
Here is my component.
import React from 'react'
import {
BrowserRouter as Router,
Switch,
Route
} from 'react-router-dom'
import { Nav } from '../../Nav'
import { Home } from '../Home'
import { Admin } from '../Admin'
import './index.scss'
export const App = () => {
return (
<Router>
<main className="main-container">
<Nav />
<Switch>
<Route path="/admin">
<Admin />
</Route>
<Route path="/" exact>
<Home />
</Route>
</Switch>
</main>
</Router>
)
}
And this is the child component that is not displaying anything even though I have the and done.
import React from 'react'
import {
Switch,
Route
} from 'react-router-dom'
import { AdminSideBar } from '../../AdminSideBar'
import { AddNewStrategy } from '../AddNewStrategy'
export const Admin = () => {
return (
<div className="container-fluid">
<div className="row">
<AdminSideBar />
<div className="col-md-9 ml-sm-auto col-lg-10 px-md-4">
<Switch>
<Route path="/add-new-strategy">
<AddNewStrategy />
</Route>
<Route path="/" exact>
Dashboard should show up here...
</Route>
</Switch>
</div>
</div>
</div>
)
}
Any help would be appreciated.
Try:
import { BrowserRouter, Route, Switch } from 'react-router-dom'
<BrowserRouter>
<Switch>
<Route path="/add-new-strategy" component={AddNewStrategy} />
<Route path="/" exact component={Dashboard} />
</Switch>
</BrowserRouter>
I found out that using useRouteMatch to specify the full path worked.
import React from 'react'
import {
Switch,
Route,
useRouteMatch
} from 'react-router-dom'
import { AdminSideBar } from '../../AdminSideBar'
import { AddNewStrategy } from '../AddNewStrategy'
export const Admin = () => {
const { path } = useRouteMatch()
return (
<div className="container-fluid">
<div className="row">
<AdminSideBar />
<div className="col-md-9 ml-sm-auto col-lg-10 px-md-4">
<Switch>
<Route path={`${path}/add-new-strategy`}>
<AddNewStrategy />
</Route>
<Route path={path} exact>
Dashboard should show up here...
</Route>
</Switch>
</div>
</div>
</div>
)
}
there are 2 more methods to route from a component to next,
<Route path="/add-new-strategy" component={AddNewStrategy} />
or
<Route path="/" exact render={()=>{<AddNewStrategy/>}} />
for nested routing define full url and give exact 'true'
<Route path="/firstcomponent/secondcomponent" exact component={AddNewStrategy} />
please try these

React router multiple <Switch>

I'm using "react-router-dom": "^4.3.1",, "#material-ui/core": "^3.9.2",
I got router.ts which has Route, Switch and MainPage component.
router.ts
<HashRouter>
<div id="App">
<Appbar />
<Switch>
<Route exact path="/" component={MainPage} />
<Route exact path="/signup" component={SignupPage} />
<Route exact path="/signup/success" component={SignupSuccessPage} />
<Route exact path="/room/:id" component={NovelPage} />
<Route component={NotfoundPage} />
</Switch>
</div>
</HashRouter>
And I got MainPage component which has Route and Switch
<AppBar position="static">
<Tabs
variant="fullWidth"
value={this.state.value}
indicatorColor="primary"
textColor="primary"
>
<Tab
label={"latest_novel"}
onChange={this.handleTabsChange(`/latest/novel`, 0)}
/>
<Tab
label={"create novel"}
onChange={this.handleTabsChange(`/create/room`, 1)}
/>
</Tabs>
<Switch>
<Route exact path={`${this.props.match.url}/latest/novel`} component={TodayNovelPage} />
<Route exact path={`${this.props.match.url}/create/room`} component={CreateRoomPage} />
</Switch>
</AppBar>
I expected
When I click Mainpage's Tab component then, page url is changed like "localhost:3000/latest/novel" and page is moved.
When page is moved then it shows under MainPage's Switch
But, when I tried it.
Page is moved but, Tabs is disappears and it seemed shows under the router.ts not Mainpage. Why is it?
Please let me know if you need more info.
Thanks.
You should have common parent route for both the tabs.
Please check below example which I have created.
https://codesandbox.io/s/zz2xnn9p7m
Index.js
import React from "react";
import ReactDOM from "react-dom";
import { Switch, Route, Redirect, BrowserRouter } from "react-router-dom";
import AppBar from "#material-ui/core/AppBar";
import Toolbar from "#material-ui/core/Toolbar";
import Typography from "#material-ui/core/Typography";
import MainPage from "../src/mainPage";
import "./styles.css";
function App() {
return (
<div className="App">
<AppBar position="static">
<Toolbar>
<Typography variant="h6" color="inherit">
Relay Novel
</Typography>
</Toolbar>
</AppBar>
<BrowserRouter>
<Switch>
<Route exact path="/" render={() => <Redirect to="/mainPage" />} />
<Route path="/mainPage" component={MainPage} />
</Switch>
</BrowserRouter>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
MainPage.js
import React, { Fragment } from "react";
import ReactDOM from "react-dom";
import { Switch, Route, Redirect, Link } from "react-router-dom";
import Tabs from "#material-ui/core/Tabs";
import Tab from "#material-ui/core/Tab";
class MainPage extends React.Component {
constructor(props) {
super(props);
this.state = {
value: 0
};
}
handleChange = (event, value) => {
this.setState({ value });
};
render() {
const { value } = this.state;
return (
<Fragment>
<Tabs value={value} onChange={this.handleChange}>
<Tab label="Latest Novel" component={Link} to="/mainPage/tab1" />
<Tab label="Create Novel" component={Link} to="/mainPage/tab2" />
</Tabs>
<Switch>
<Route exact path="/mainPage" />
<Route path="/mainPage/tab2" render={() => <div>Latest Novel</div>} />
<Route path="/mainPage/tab1" render={() => <div>Create Novel</div>} />
</Switch>
</Fragment>
);
}
}
export default MainPage;

React router params not passing

I am trying to go to page and pass id, but my ownParams in mapStatetoProps remains empty. Do note the url generated is correct.
My current structure is
index.jsx
ReactDOM.render(
<Provider store={createStoreWithMiddleware(reducers)}>
<BrowserRouter>
<App />
</BrowserRouter>
</Provider>,
document.getElementById('root')
);
app.jsx
class App extends Component {
render() {
return (
<div>
<Nav />
<Router />
</div>
);
}
}
export default App;
router.jsx
const Router = () => (
<router>
<Switch>
<Route path='/signup' component={Signup} />
<Route path='/browse' component={Browse}/>
<Route path='/detail/:id' component={Detail} />
</Switch>
</router>
)
export default Router;
detail.jsx
export default class App extends Component {
render() {
return (
<div>
<Info />
<Message/>
</div>
);
}
}
Now in info.jsx at the end of the file
function mapStateToProps({posts}, ownProps){
console.log('>>>>>>>> post_show State to props', ownProps);
return { post : posts[ownProps.match.params.id]};
}
export default connect(mapStateToProps, { fetchPost, deletePost })(PostsShow);
this gives me an error because ownProps is empty.

React Router, this.props.children == null when refreshing page

So I've got React-Router set up as follows:
// App.js
render(){
console.log(this.props)
return (
<div>
<Header />
{this.props.children}
<Footer />
</div>
)
}
// routes.js
export default (
<Router>
<Route path="/" component={App} >
<IndexRoute component={HomePage} />
<Route path="/destination" component={DestinationIndexHandler} />
<Route path="/destination/:slug" component={DestinationHandler} />
<Route path="/destination/:slug/:article" component={ArticleHandler} />
<Route path="/article" component={ArticleIndexHandler} />
<Route path="/article/:slug" component={ArticleHandler} />
<Route name='contact' path="/contact" component={ContactHandler} />
<Route path="*" component={NotFoundHandler}/>
</Route>
</Router>
);
And finally:
// server.js
app.get(/^(?!.*(api|.js|.css)).*$/, function (req, res) {
let location = createLocation(req.url);
console.log("LOCAL")
match({ routes, location }, (error, redirectLocation, renderProps) => {
console.log(location);
if (error) {
res.status(500).send(error.message)
} else if (redirectLocation) {
res.redirect(302, redirectLocation.pathname + redirectLocation.search)
} else if (renderProps) {
let content = ReactDOMServer.renderToString(<RoutingContext {...renderProps} />);
console.log(content);
res.status(200).render('index', {
content: content,
staticPort: DEV_PORT
});
} else {
res.status(404).send('Not found')
}
})
});
For some reason, when I refresh the page, the server side renders the view successfully but the browser renders the home view (IndexRoute). If you remove the IndexRoute, it only renders the header and footer.
If I log in the terminal I get 3 returns, the first and second being:
CHILDREN { '$$typeof': Symbol(react.element),
type: [Function: ContactHandler],
and finally a call to NotFoundHandler, even though the route is there. Any ideas?