implement auth route - with nested /signin and /signup routes - javascript

I want to implement simple signin/signup routs with react-router-dom.
here is the App.js
<div className="App">
<Switch>
<Route exact path="/" component={HomePage} />
<Route exact path="/auth" component={SignInSignUpPage} />
</Switch>
</div>
and I'm using route guard like so :
const HomePage = ({ currentUser }) => {
return currentUser ? <HomePageComponent /> : <Redirect to="/auth" />;
};
Now what I want to do, is to have the /auth route, for users to login, and inside a container of both the signInSignUp, I have a Link that will change the route to auth/signup to view the signup page like so:
const SignInSignUpPage = ({ match }) => {
return (
<SignInSignUpContainer>
<Route path={`${match.path}`} component={SignIn} />
<Route path={`${match.path}/signup`} component={SignUp} />
</SignInSignUpContainer>
);
};
which in turn will render the correct component :
const SignInSignUpContainer = ({ match, children, history }) => {
const { isExact } = match;
return (
<SignInSignUpContainerContent>
<SignInSignUpContainerContentForm>
<LockIconContainer iconName="icon-lock-closed" />
{children}
</SignInSignUpContainerContentForm>
</SignInSignUpContainerContent>
);
};
I must be doing it wrong, and the react-router-dom docs are addressing the protected route, which I didn't find suitable for this case.

Just from looking at the structure: you don't send match prop down to SignInSignUpContainer, and it seems that SignInSignUpContainer expects it.
should be :
const SignInSignUpPage = ({ match }) => {
return (
<SignInSignUpContainer match={match}>
<Route path={`${match.path}`} component={SignIn} />
<Route path={`${match.path}/signup`} component={SignUp} />
</SignInSignUpContainer>
);
};
same with history prop

Related

Creating routes inside a private route with react-router v6

Currently using react-router-dom 6.1.1 and I'm working with a private route.
Inside this private route I usually had other routes (so that I can keep my Sidebar on them).
My code looks like this
// App.tsx
const RequireAuth: React.FC<PrivateRouteProps> = ({ children, redirectTo }) => {
const isAuthenticated = Auth.isLogedIn()
return isAuthenticated ? children : <Navigate to={redirectTo} />
}
const Page = () => {
return (
<div className={css.host}>
<BrowserRouter>
<Routes>
<Route path="/login" element={<Login />} />
<Route
path="/"
element={
<RequireAuth redirectTo="/login">
<Home />
</RequireAuth>
}
/>
</Routes>
</BrowserRouter>
</div>
)
}
// Home/index.tsx
const Home = () => {
return (
<div className={css.host}>
<Sidebar sections={sidebarOptions(t)} />
<Routes>
{routes.map(({ breadCrumbtitle, link, component }, index) => (
<Route path={link} key={index}>
{component ? component : <p>[{breadCrumbtitle}] To be done</p>}
</Route>
))}
</Routes>
</div>
)
}
So... This setup worked with v5 but it seems to be something that doesn't really work with v6.
What can I do if I still want to keep the Sidebar for all the routes once I'm logged in?
I ended up finding the solution to my issue.
Doing what Drew Reese suggested only worked to a certain point since I was being led to a route that, for react router, didn't exist.
For it to work I add to do
// App.tsx
const RequireAuth: React.FC<PrivateRouteProps> = ({ children, redirectTo }) => {
const isAuthenticated = Auth.isLogedIn()
return isAuthenticated ? children : <Navigate to={redirectTo} />
}
const Page = () => {
return (
<div className={css.host}>
<BrowserRouter>
<Routes>
<Route path="/login" element={<Login />} />
<Route
path=""
element={
<RequireAuth redirectTo="/login">
<Home />
</RequireAuth>
}
>
{routes.map(({ breadCrumbtitle, link, component }, index) => {
return <Route path={link} key={index} element={component}></Route>
})}
</Route>
</Routes>
</BrowserRouter>
</div>
)
}
// Home/index.tsx
const Home = () => {
return (
<div className={css.host}>
<Sidebar sections={sidebarOptions(t)} />
<div className={css.contentContainer}>
<Outlet />
</div>
</div>
)
}
Using the Outlet seemed to be essential, don't know if it's something new on react router v6 but seemed to do the trick!
As far as I can tell the only issue is with the routes mapping, the Route components have invalid children, i.e. you are rendering another Route or React.Fragment as a child.
Move this up to the element prop of the mapped Route components.
const Home = () => {
return (
<div className={css.host}>
<Sidebar sections={sidebarOptions(t)} />
<Routes>
{routes.map(({ breadCrumbtitle, link, component }, index) => (
<Route
path={link}
key={index}
element={component || <p>[{breadCrumbtitle}] To be done</p>}
/>
))}
</Routes>
</div>
);
};

React Authentication "fails" on refresh in protected Route

I've came across a problem with protected routes and the authentication of protected routes.
I'm using an AuthContext for the whole user authentication across the webapp. I save all the user-data inside a state. With the help of useEffect and the sessionStorage I store the user object so It can be used after a page reload.
[...]
export const AuthProvider = ({ children }) => {
const [user, setUser] = useState({})
useEffect(() => {
const sessionUser = sessionStorage.getItem("user")
if(!sessionUser) return
changeUser(JSON.parse(sessionUser))
}, [])
const hasRank = (ranks) => {
if(!Object.keys(user).length > 0) return false
const matchingPerms = ranks.filter(rank => user.rank.includes(rank))
return matchingPerms.length > 0
}
const changeUser = (data) => {
setUser(data)
if(Object.keys(data).length > 0) {
return sessionStorage.setItem("user", JSON.stringify(data))
}
return sessionStorage.removeItem("user")
}
}
[...]
In order to protect certain pages I'm using a Protected Route Component that checks whether the user is logged in or not.
[...]
const auth = useContext(AuthContext)
const isAuthorized = auth.hasRank(rest.rank)
<Route
{...rest}
render={props => {
return isAuthorized ? (
<Component {...props} />
) : (
<Redirect to="/auth/login" />
)
}}
/>
The saving and fetching into and from the sessionStorage works fine until I want to render content that's inside a protected route - I always get redirected to the login page, because the user object is empty because of the reload and the state is not being updated early enough. Therefore the protected route checks for authentication with an empty object, which results in a redirect to the login page.
How can I wait until the user state is being updated before checking the authentication inside the protected route?
EDIT:
App Component:
return (
<Router>
<Switch>
<Route path="/auth/register" component={Register} />
<Route path="/auth/login" component={LogIn} />
<Route path="/err/404" component={Page404} />
<Route path="/" component={PanelRoutes}/>
</Switch>
</Router>
)
PanelRoutes Component:
return (
<div className="skin-default fixed-layout">
<div id="main-wrapper">
<Topbar />
<Sidebar />
<div className="page-wrapper">
<div className="container-fluid">
<Switch>
<Route exact path="/" component={Homepage} />
<Route exact path="/legal/imprint" component={Imprint} />
<Route exact path="/legal/privacy" component={Privacy} />
<ProtectedRoute exact path="/mc/sounds" component={McSounds} rank={["ADMIN", "MC"]} />
<ProtectedRoute exact path="/admin/users" component={AdminUsers} rank={["ADMIN"]} />
</Switch>
</div>
</div>
</div>
</div>
)
Kind Regards

Trouble in configuring nested routes in react router dom v5

I'm having trouble configuring my react-router I'm building a dashboard with 2 pages with nested routes. The first page is the main page with all the protected routes and the second login with unprotected routes. The problem I'm facing is when I place the exact prop on AuthRoute in the Routes component I won't be able to navigate to all the protected routes but I can navigate to login page. If I remove the exact prop I'll be able to navigate to all the protected routes but I can't navigate to the login page. I tried purring the exact on both components it won't work as well
this is may Routes component with two pages Main with all protected routes such as Dashboard and logIn page with another route reset-page
function Routes({ ColorModeContext }) {
return (
<Router>
<Switch>
<AuthRoute
path="/"
ColorModeContext={ColorModeContext}
component={Main}
/>
<Route path="/login" component={Login} />
<Route path="*">
<div className="center">
<Box pb={2}>
<Typography variant="h3">Page Not Found (-_-)</Typography>
</Box>
<Button variant="contained" onClick={() => window.history.back()}>
Go Back
</Button>
</div>
</Route>
</Switch>
</Router>
);
}
this is my AuthRoute that separates protected routes from unprotected routes
const AuthRoute = ({ component: Component, authStatus = true, ...rest }) => {
const { ColorModeContext, ...other } = rest;
return (
<Route
{...other}
render={(props) =>
authStatus ? (
<Component {...props} ColorModeContext={ColorModeContext} />
) : (
<Redirect to="/login" />
)
}
/>
);
};
here's the main page with all the routes
const Main = ({ props }) => {
const { path } = useRouteMatch();
return (
<Box component="main" sx={{ flexGrow: 1, p: 3 }}>
<DrawerHeader />
<Switch>
<Route exact path={path} component={Home} />
<Route path={`${path}/exam-results`} component={ExamResults} />
<Route path={`${path}/new-reg`} component={Registration} />
//...more components
</Switch>
</Box>
);
};
here's the login page
const LogIn = ({ props }) => {
const { path } = useRouteMatch();
return (
<Switch>
<Route exact path={path} component={LogInForm} />
<Route path={`${path}/reset-password`} component={ResetPassForm} />
</Switch>
);
};

Routes not working as desired in React JS

I am trying to build a full stack application with User login/logout functionality.
I want to protect certain pages such that they can only be viewed when the user is logged in. For login I have created a REST API and I am using session storage to keep track of whether the user is logged in or not.
validateUser = () => {
let user = {
username: this.state.email,
password: this.state.password,
//status: "LOGGED_IN"
};
UserService.authenticateUser(user).then((res) => {
if(res.data === 'SUCCESS') {
window.sessionStorage.setItem("isUserLogged", true);
} else if(res.data === 'FAILURE') {
window.sessionStorage.setItem("isUserLogged", false);
this.resetLoginForm();
this.setState({"error":"Invalid username or password"});
}
})
};
Tis is my App.js
function App() {
return (
<div>
<Router>
<HeaderComponent/>
<div className="container">
<Switch>
<Route path="/" exact component={LandingPageComponent}></Route>
{/* <Route path ="/customers" component = {ListCustomerComponent}></Route> */}
{/* <Route path ="/add-customer/:id" component = {CreateCustomerComponent}></Route> */}
<Route path = "/view-customer/:id" component = {ViewCustomerComponent}></Route>
<Route path = "/admin-login" component = {AdminLoginComponent}></Route>
<Route path = "/admin-register" component = {AdminResgisterComponent}></Route>
<Route path="/customers" exact render={() => (
window.sessionStorage.getItem("isUserLogged") === "true"
? <ListCustomerComponent />
: <Redirect to='/admin-login' />
)} />
<Route path="/add-customer/:id" exact render={() => (
window.sessionStorage.getItem("isUserLogged") === "true"
? <CreateCustomerComponent />
: <Redirect to='/admin-login' />
)} />
</Switch>
</div>
<FooterComponent/>
</Router>
</div>
);
}
export default App;
Everything works fine if I don't check my session storage. But when I try to implement the conditional routes as shown above I start getting errors.
If I just put simple routes, then I don't encounter this error.
Any help would be highly appreciated.
You didn't pass Route props into your component. So history does not included in props, you can console.log(this.props) to check what this.props contains.
To fix it, let's pass Route props into your components
<Route path="/add-customer/:id" exact render={(props) => (
window.sessionStorage.getItem("isUserLogged") === "true"
? <CreateCustomerComponent {...props} /> // ADD PROPS HERE
: <Redirect to='/admin-login' />
} />
You didn't show what you did on ListCustomerComponent.
You could try to encapsulate your component using HOC withRouter or if you are using Functional component, use useHistory hook.
// on export class component
export default withRouter(YourComponent)
in functional component, you can use
const YourComponent = ()=>{
const history = useHistory();
// then you can say something such as
// history.push(...)
return <>...your view here...</>
}
export default YourComponent;
<Switch>
{/* Login Sections goes Here */}
<Route exact path='/' component={MainPage} />
<Route exact path='/login' component={Login} />
<Route exact path='/admin/' component={LoginAdmin} />
<Route exact path='/register' component={Register} />
{/* AdminUser ROutes goes here */}
<SuperUserDashboard>
<Route exact path='/admin/dashboard' component={Dashboardpage} />
<Route exact path='/admin/users' component={UsersAdmin} />
</SuperUserDashboard>
<Route exact path='' component={Notfound} />
</Switch>
in superuser dashboard check if user is authenticated if not redirect to admin login page else all the routes will be visible

Private Route React router always redirects to first Route

Actually I had no problem with directing to another route by clicking a button, but somehow I can't direct manually by changing the URL. Every time I was about changing the URL (ex: localhost:3000/proposal), it always directs me to the first Route. Here's the Route in order :
<Switch>
<Route exact path="/" component={Landing} /> // => always goin here
<Route exact path="/login" component={Login} />
<Route exact path="/register" component={Register} />
{/* Dashboard */}
<PrivateRoute
exact
path="/home"
component={Home}
StickyNav={StickyNavbar}
/>
<PrivateRoute
exact
path="/proposal"
component={Proposal}
StickyNav={StickyNavbar}
/>
<PrivateRoute
exact
path="/laporan"
component={Laporan}
StickyNav={StickyNavbar}
/>
<Route component={NotFound} />
</Switch>
It doesn't direct me to Landing if I change the URL to non-private route. Here's my private route code :
import React from "react"
import { Route, Redirect } from "react-router-dom"
import { connect } from "react-redux"
import PropTypes from "prop-types"
const mapStateToProps = state => ({
auth: state.auth
})
const PrivateRoute = ({ component: Component, auth, ...rest }) => (
<Route
{...rest}
render={props =>
auth.isAuthenticated === true ? (
<Component {...props} {...rest} />
) : (
<Redirect to="/login" />
)
}
/>
)
PrivateRoute.propTypes = {
auth: PropTypes.object.isRequired
}
export default connect(mapStateToProps)(PrivateRoute)
Based on #zhuber said, the auth object from react-redux doesn't call before the private route was called. So I changed the condition from isAuthenticated using localStorage like this :
!isEmpty(localStorage.jwtToken) ? (
<Component {...props} {...rest} />
) : (
<Redirect to="/login" />
)

Categories