I am attempting to implement a private route in React; the homepage should not be visible until the user logs in. If I restart my frontend, all protected routes are not accessible until the user logs in. However, after the first login, all protected routes don't seem to be protected; I can logout, the session is destroyed in my database and my backend sends a response of {isLoggedIn: false}, but for some reason I can still access the protected routes.
When I didn't use 'useState', I could login and my backend would confirm I was logged in, but I still couldn't access any protected routes. This is the closest I've got to my end goal, but obviously still doesn't work. Any help would be appreciated.
Private Routes
import { useState, useEffect } from 'react';
import React from 'react';
import axios from 'axios';
const checkIfLogged = async () => {
let[logged, setLogged] = useState(false);
await axios.get("http://localhost:3001/auth", {
withCredentials: true
}).then((res) => {
setLogged(res.data.isLoggedIn);
})
return logged;
}
const updateAuth = async(check) => {
const loggedIn = await check;
return loggedIn;
}
const PrivateRoutes = () =>{
const loggedIn = updateAuth(checkIfLogged);
return(
loggedIn ? <Outlet /> : <Navigate to="/login" />
)
}
export default PrivateRoutes;
Auth Check
app.get("/auth", (req, res) => {
if(req.session.isAuth){
res.send({isLoggedIn: true})
}
else{
res.send({isLoggedIn: false})
}
})
App.js
import{
Routes,
Route,
} from "react-router-dom";
import React from "react";
import Home from "./screens/Home";
import About from "./screens/About";
import Login from "./screens/Login";
import Register from "./screens/Register";
import Logout from "./screens/Logout";
import PrivateRoutes from "./utils/private";
const App = () => {
return (
<>
<Routes>
<Route element={<PrivateRoutes/>}>
<Route path="/" exact element={<Home />}/>
<Route path="/about" element={<About />}/>
</Route>
<Route path="/login" element={<Login />} />
<Route path="/register" element={<Register />}/>
<Route path="/logout" element={<Logout />}/>
</Routes>
</>
);
}
export default App;
Another solution is by actually make a conditional rendering based on if the user is admin, then the page is rendered, otherwise a null is returned:
so in your PrivateRoutes:
import { useState, useEffect } from 'react';
import React from 'react';
import axios from 'axios';
const PrivateRoute = async () => {
let[logged, setLogged] = useState(false);
let [isSuperUser,setIsSuperUser] = useState(false) // adding new state
useEffect(()=>{
axios.get("http://localhost:3001/auth", {
withCredentials: true
}).then((res) => {
setLogged(res.data.isLoggedIn);
setIsSuperUser(res.data.isSuperUser)
})
},[isLoggedIn])
return (
isLoggedIn&& isSuperUser?<Outlet/>:null
)
}
export default PrivateRoutes;
Edit: this solution is valid, however, I added a 2nd solution that match the OP style.
First of all, checkLoggedIn is a component, so it need to be capitalized, fix it to CheckLoggedIn
Second of all, I am not sure what backend you are using, but I would check if the user is an admin/superuser all in App.js, since we are dealing with routes here, and then based on that I will let them access the protected route:
in App.js
import{
Routes,
Route,
} from "react-router-dom";
import React from "react";
import {useEffect,useState} from React;
import Home from "./screens/Home";
import About from "./screens/About";
import Login from "./screens/Login";
import Register from "./screens/Register";
import Logout from "./screens/Logout";
import PrivateRoutes from "./utils/private";
const App = () => {
let[logged, setLogged] = useState(false)
let [isSuperUser,setIsSuperUser] = useState(false)
useEffect(()=>{
axios.get('localhost:3001/auth/',{withCredentials:true}).then((res)=>{
// send a response if the current logged in user is a super user
setLogged(res.data.isLoggedIn)
setIsSuperUser(res.data.isSuperUser)
}
},[logged]) // useEffect will get re-invoked whenever the user log out, or their session is ended
// now for the last part, I would make a conditional rendering, where a user will access route only if they are superuser/admin
return (
<>
<Routes>
<Route element={isSuperUser?<PrivateRoutes/>:''}>
<Route path="/" exact element={<Home />}/>
<Route path="/about" element={<About />}/>
</Route>
<Route path="/login" element={<Login />} />
<Route path="/register" element={<Register />}/>
<Route path="/logout" element={<Logout />}/>
</Routes>
</>
);
}
export default App;
Notice the first route, you will only be able to access it if you are a logged in admin.
you also need to notice that what define a react component is 2 things:
Capitalization of your functional component name
it has to return a JSX
Related
Given is an application with 3 pages:
"Mainpage"
"PrivatPage"
"UserManagementPage"
After successful login the user is redirected to a "PrivatPage". On the "PrivatPage" the user has the possibility to return to the "MainPage" or to go to the "UserManagementPage". The part with the redirecting from the "MainPage" to the "PrivatPage" works.
The code looks like this:
import PublicPage from "./components/PublicPage";
import PrivatePage from "./components/PrivatePage";
import UserManagementPage from "./components/UserManagementPage";
import React, { useState, Component } from "react";
import { connect } from "react-redux";
const mapStateToProps = state => {
return state;
};
class App extends Component {
render() {
const accessToken = this.props.accessToken;
console.log(accessToken);
let workspace;
if (accessToken) {
workspace = <PrivatePage />;
} else {
workspace = <PublicPage />;
}
return <div className="App">{workspace}</div>;
}
}
export default connect(mapStateToProps)(App);
But how do I use the conditionals to get to the "UserManagementPage" ?
if you consider functional components, you could use BrowserRouter as follows with react-router-dom.
If you need to handle authentication, you can f.e. build a custom <PrivateRoute /> component and use this on your protected routes instead of <Route />. I always keep these routes in a separate file and import them in App.js.
Here for demo purposes routes in App.js:
import { BrowserRouter as Router } from "react-router-dom";
// import your page components
// and add everything else you want to add to your component
const App = () => {
return (
<>
<Router>
<Routes>
<Route path="/login" element={<Login />} />
<Route path="/private" element={<PrivatePage />} />
<Route path="/public" element={<PublicPage />} />
<Route path="/user" element={<UserManagementPage />} />
</Routes>
</Router>
</>
);
};
export default App;
Adding on to private blocks answer you would then in your components use the
<Redirect to='/your-route' />
You would then create a boolean state variable and once it return true you could redirect immediatley like this (where you are rendering jsx):
render() {
{booleanState && <Redirect to='/your-route' />}
}
I have a react project and I am working on navbar element of the project. I am currently implementing the react-router-dom in the project. I have the <Route/> nested in the <Routes/>. All of it is contained in the <BrowserRoutes/>. It is rendering the navbar. For the / it is supposed to check and see if loggedIn is true or false and display different components based on if it is true of false.
If it is false, it is supposed to show a login component with the login page. If the user is logged in, it is supposed to show the feed component.
Right now what it is doing is the user is not logged in but it is showing a blank screen and giving me the following error:
Uncaught Error: [LandingPage] is not a <Route> component. All component children of <Routes> must be a <Route> or <React.Fragment>
Here is the code I have:
import React, {useState} from 'react';
import Login from './Login';
import Signup from './Signup'
import LandingPage from './LandingPage'
import Feed from './Feed'
import axios from 'axios';
import { Routes, Route } from 'react-router-dom';
axios.defaults.baseURL = 'http://127.0.0.1:3333';
axios.defaults.headers.common['Authorization'] = 'AUTH TOKEN';
axios.defaults.headers.post['Content-Type'] = 'application/json';
export const ContentContext = React.createContext()
export default function App() {
const [loggedIn, setLoggedIn] = useState(false)
return (
<div className="App">
<ContentContext.Provider value={ContentContextValue}>
<Routes>
<Route exact path="/">
{loggedIn ? <Feed /> : <LandingPage />}
</Route>
<Route exact path="/login" element={<Login/>}/>
<Route exact path="/signup" element={<Signup/>}/>
</Routes>
</ContentContext.Provider>
</div>
);
}
Just try to call the pages inside element for route, try like this:
<Route exact path="/" element={loggedIn ? <Feed /> : <LandingPage />} />
I am working on rerouting my application. I have a form, I want the user to be redirected after the form is submitted successfully. I placed the logic after the api is successful. My url changes but the component that is meant to be loaded on that url does not load.
import { browserHistory } from '../router/router';
...
export const storeRecord=(data)=>{
return function(dispatch, getState){
//console.log('state',getState());
const {authToken, userId} = getState().authReducer;
token= authToken;
data.userId = userId;
const url = 'investment/store';
apiCall(url, data, dispatch,'post').then((data)=>{
try{
if(data.status=== 200){
//let data = apiResponse.data
console.log('success',data);
browserHistory.push('/');
//dispatch(push('/'));
}
}
catch(error){
console.log('returnedError',error);
}
}, (error)=>{console.log(error)});
}
}
Here is my route as well:
import React from 'react';
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
import { createBrowserHistory } from 'history';
...
export const browserHistory = createBrowserHistory();
const AppRouter=()=>{
return(<Router>
<LoaderModal />
<Switch>
<Route path="/" exact component={LandingPage} />
<PublicRouter path="/register" exact component={SignupPage} />
<PublicRouter path="/login" exact component={LoginPage} />
<PrivateRoute path="/investment/new" exact component={InvestmentForm} />
<Route component={NotFoundPage} />
</Switch>
</Router>)
}
export default AppRouter;
I am using react-router-dom for rerouting and history for the browser re-routing.
You can use following code in place of dispatch(push('/')) which you used in storeRecord component.
window.location.assign('/');
You need to use the same history object that your Router modules uses to dynamically Route.
The easiest solution is for you to not use browserRouter but to use a Router with custom history
import React from 'react';
import { Router, Route, Switch } from 'react-router-dom';
import { createBrowserHistory } from 'history';
...
export const browserHistory = createBrowserHistory();
const AppRouter=()=>{
return(<Router history={browserHistory}>
<LoaderModal />
<Switch>
<Route path="/" exact component={LandingPage} />
<PublicRouter path="/register" exact component={SignupPage} />
<PublicRouter path="/login" exact component={LoginPage} />
<PrivateRoute path="/investment/new" exact component={InvestmentForm} />
<Route component={NotFoundPage} />
</Switch>
</Router>)
}
export default AppRouter;
and then your action will look like
import { browserHistory } from '../router/router';
...
export const storeRecord=(data)=>{
return function(dispatch, getState){
const {authToken, userId} = getState().authReducer;
token= authToken;
data.userId = userId;
const url = 'investment/store';
apiCall(url, data, dispatch,'post').then((data)=>{
try{
if(data.status=== 200){
browserHistory.push('/');
}
}
catch(error){
console.log('returnedError',error);
}
}, (error)=>{console.log(error)});
}
}
I have a react app. It is working fine. It uses redux,react-router 3. The routes work fine, but when I press the back button, they route gets duplicated. For example from localhost:3000/admin/main which I am currently, when I go back, it goes to localhost:3000/admin/admin/main, which return not found.
Here is my routes code:
export default (
<Route path="/" component={App}>
<Route path="home" component={requireNoAuthentication(HomeContainer)} />
<Route path="login" component={requireNoAuthentication(LoginView)} />
<Route exact path="admin/user" component={requireAuthentication(UserView)} />
<Route exact path="admin/main" component={requireAuthentication(UsersListView)} />
<Route path="secure" component={requireAuthentication(CustomerView)} />
<Route exact path="*" component={DetermineAuth(NotFound)} />
</Route>
);
I also get a console error: Adjacent JSX elements must be wrapped in an enclosing tag. If anyone can help it would be great thanks!!
Your HOC wrappers (requireNoAuthentication and requireAuthentication) and using exact (I think this might a react-router v4 only feature?) might be messing with your route history. Try restructuring your routes so that all of them fall under App -- some of the routes fall under RequireAuth, while the rest are public.
As a side note: you can avoid using React.cloneElement with passed down class methods and state by using Redux instead.
routes/index.js
import React from "react";
import { browserHistory, IndexRoute, Router, Route } from "react-router";
import App from "../components/App";
import Home from "../components/Home";
import Info from "../components/Info";
import ShowPlayerRoster from "../components/ShowPlayerRoster";
import ShowPlayerStats from "../components/ShowPlayerStats";
import Schedule from "../components/Schedule";
import Sponsors from "../components/Sponsors";
import RequireAuth from "../components/RequireAuth";
export default () => (
<Router history={browserHistory}>
<Route path="/" component={App}>
<Route component={RequireAuth}>
<IndexRoute component={Home} />
<Route path="roster" component={ShowPlayerRoster} />
<Route path="roster/:id" component={ShowPlayerStats} />
<Route path="schedule" component={Schedule} />
</Route>
<Route path="info" component={Info} />
<Route path="sponsors" component={Sponsors} />
</Route>
</Router>
);
index.js
import React from "react";
import { render } from "react-dom";
import App from "../routes";
import "uikit/dist/css/uikit.min.css";
render(<App />, document.getElementById("root"));
components/App.js
import React, { Component, Fragment } from "react";
import { browserHistory } from "react-router";
import Header from "./Header";
export default class App extends Component {
state = {
isAuthenticated: false
};
isAuthed = () => this.setState({ isAuthenticated: true });
unAuth = () =>
this.setState({ isAuthenticated: false }, () => browserHistory.push("/"));
render = () => (
<Fragment>
<Header
isAuthenticated={this.state.isAuthenticated}
unAuth={this.unAuth}
/>
{React.cloneElement(this.props.children, {
isAuthenticated: this.state.isAuthenticated,
isAuthed: this.isAuthed
})}
</Fragment>
);
}
components/RequireAuth.js
import React, { Fragment } from "react";
import Login from "./Login";
export default ({ children, isAuthenticated, isAuthed }) =>
!isAuthenticated ? (
<Login isAuthed={isAuthed} />
) : (
<Fragment>{children}</Fragment>
);
I'm using Meteor.loginWithFacebook. All is working well except after login the user is redirected back to the / page. Instead I would like to redirect the user to /dashboard.
Is this possible to do from meteor, or do I have to create a <Route> that will handle this? I am using react-router. Here's my routes:
import React from 'react'
import { Router, Route, Switch, Redirect } from 'react-router'
import createBrowserHistory from 'history/createBrowserHistory'
import { withTracker } from 'meteor/react-meteor-data'
import { Meteor } from 'meteor/meteor'
import HomePage from '../../ui/pages/HomePage.js'
import DashboardPage from '../../ui/pages/DashboardPage.js'
import NotFoundPage from '../../ui/pages/NotFoundPage.js'
const browserHistory = createBrowserHistory();
const ProtectedRoute = ({
component: Component,
user,
loggingIn,
...rest
}) =>
(!user && loggingIn && <div>Loading</div>) ||
(user && <Route {...rest} render={props =>
<Component {...props} user={user} />}
/>) ||
<Redirect to="/"/>
const Routes = (props) => (
<Router history={browserHistory}>
<Switch>
<ProtectedRoute exact path="/dashboard" component={DashboardPage} {...props} />
<Route exact path="/" component={HomePage} />
<Route component={NotFoundPage} />
</Switch>
</Router>
)
export default withTracker((props) => {
return {
user: Meteor.user(),
loggingIn: Meteor.loggingIn()
}
})(Routes)
I figured it out. The documentation shows loginWithFacebook accepts a redirectUrl property. I simply set this to the dashboard URL. After the user has logged in they will be redirected to the dashboard.
To show an example here is the click handler for button component I created. When the user clicks they are redirected to accept the Facebook application permissions, once complete they will be redirected to the dashboard route:
handleClick = () => {
Meteor.loginWithFacebook(
{
requestPermissions: this.props.permissions,
loginStyle: this.props.loginStyle,
redirectUrl: Meteor.absoluteUrl('dashboard'),
},
err => {
// These will be unsed when using redirect flow
if (err) return this.props.onError(err)
return this.props.onSuccess()
}
)
}