I have a small react app which has user login, I want to have a private route which will be protected, and only authenticated users can use it.
Here is my code.
App.js
import React, { Component } from 'react';
import { BrowserRouter as Router, Route, Switch } from "react-router-dom";
// Styling
import './App.css';
// Privat route
import PrivateRoute from './routes/PrivateRoute';
// Common
import Navigation from './components/common/Navigation';
// Components
import Layout from './components/Layout'
import Login from './components/Login';
import Home from './components/Home';
import Profile from './components/Profile';
export default () => (
<Router>
<Switch>
<Layout>
{/* Public routes */}
<Route exact path="/" component={ Home } />
<Route exact path="/login" component={ Login } />
{/* Private routes */}
<PrivateRoute exact path="/profile" component={ Profile } />
</Layout>
</Switch>
</Router>
);
PrivateRoute.js
import React, { Component } from 'react';
import { Route, Redirect } from 'react-router-dom';
import UserService from '../services/User';
export default class PrivateRoute extends React.Component {
state = {
isAuthenticated: null,
user: null
}
async componentDidMount(){
let isAuthenticated, user = null;
try{
user = await UserService.auth();
isAuthenticated = true;
}catch(e){
isAuthenticated = false;
}
this.setState({ isAuthenticated, user });
}
render() {
const { isAuthenticated, user } = this.state;
if(isAuthenticated === null){
return null;
}
return (
isAuthenticated ?
<Route path={this.props.path} component={this.props.component}/> :
<Redirect to={{ pathname: '/login', state: { from: this.props.location }}} />
);
}
}
Home.js
import React, { Component } from 'react';
export default class Home extends Component{
render(){
return (
<div>Home!</div>
);
}
}
I am constantly getting the error
Warning: You tried to redirect to the same route you're currently on: "/login"
even though I am in the home page which is has no business checking if user is authenticated.
I suspect this issue has something to do with my routes and the common layout I am trying to implement, but I cant find the problem.
Related
I have two(USER and ADMIN) users in my react project and I want to redirect them to their respected page after a successful login. To achieve this I created a standalone component called authenticatedRoute.js and wrap those components by AuthenticatedRoute component. Here are the codes
AuthenticatedRoute.js
import { useHistory } from "react-router-dom";
import { Route } from "react-router-dom";
const AuthenticatedRoute = ({ children, ...rest }) => {
const history = useHistory();
var tokenn = JSON.parse(localStorage.getItem("desta"));
var user_type = tokenn?.user.roles[0]; //USER or ADMIN
var loggedIn = tokenn?.user_status; //true
const redirect = () => {
try {
switch (user_type) {
case "USER":
return [history.push("/user"), children];
case "ADMIN":
return [history.push("/admin"), children];
default:
console.log("redirect");
}
} catch (err) {
return [history.push("/signin"), children];
}
};
return (
<Route
render={() => {
return loggedIn ? redirect() : history.push("/signin");
}}
/>
);
};
export default AuthenticatedRoute;
App.js (minimal code)
import "./App.css";
import Header from "./components/Header/Header";
import HeaderLinks from "./components/Header/HeaderLinks";
import Hero from "./components/Header/Hero/Hero";
import Login from "./components/auth/Login";
import {
BrowserRouter as Router,
Route,
Switch,
useHistory,
} from "react-router-dom";
import SignUp from "./components/auth/SignUp";
import ViewUserProfile from "./components/Admin/ViewUserProfile";
import AdminHeader from "./components/Admin/AdminHeader";
import ManageBusinessType from "./components/Admin/ManageBusinessType";
import ManageCompany from "./components/Admin/ManageCompany";
import UserDashboard from "./components/Users/UserDashboard";
import ViewProfile from "./components/Users/ViewProfile";
import { ResetPassword } from "./components/Users/ResetPassword";
import ChangePassword from "./components/Users/ChangePassword";
import { useEffect } from "react";
import AuthenticatedRoute from "./components/auth/authRoute/AuthenticatedRoute";
function App() {
return (
<Router>
<Switch>
<AuthenticatedRoute path="/" exact>
<Hero />
</AuthenticatedRoute>
<AuthenticatedRoute path="/admin" exact>
<AdminHeader />
</AuthenticatedRoute>
<AuthenticatedRoute path="/user" exact>
<UserDashboard />
</AuthenticatedRoute>
</Switch>
</Router>
);
}
export default App;
After a successful login it redirects me to the right user role page but it's empty page as the screenshot below.
How can I fix the Issue? I used react-router-dom#5.3.0
Thanks
You are rendering the result of a navigation action instead of JSX. AuthenticatedRoute should render either a Route rendering the routed content or a Redirect to the appropriate path.
Example:
If loggedIn is falsey, then redirect to "/signin", otherwise check the role the route should have access to, and if the role matches render a Route with the props passed through, otherwise redirect to the appropriate user/admin path.
import { useHistory } from "react-router-dom";
import { Route } from "react-router-dom";
const AuthenticatedRoute = ({ roles, ...props }) => {
const tokenn = JSON.parse(localStorage.getItem("desta"));
const user_type = tokenn?.user.roles[0]; //USER or ADMIN
const loggedIn = tokenn?.user_status; //true
return loggedIn
? roles.includes(user_type)
? <Route {...props} />
: <Redirect to={user_type === "USER" ? "/user" : "/admin"} />
: <Redirect to="/signin" />;
};
export default AuthenticatedRoute;
Specify the roles a route should accessible by.
function App() {
return (
<Router>
<Switch>
<AuthenticatedRoute path="/admin" roles={["ADMIN"]}>
<AdminHeader />
</AuthenticatedRoute>
<AuthenticatedRoute path="/user" roles={["USER"]}>
<UserDashboard />
</AuthenticatedRoute>
<Route path="/">
<Hero />
</Route>
</Switch>
</Router>
);
}
I'm trying see if there is a user with firebase but every time I login into my app and console.log the user it says that there is no user and I can't log into my app.
import React,{Component} from 'react';
import SignUp from './components/Authentication/SignUp';
import {BrowserRouter as Router,Switch,Route,Link} from 'react-router-dom';
import Feed from './components/Feed/Feed';
import Search from './components/SearchUser/Search';
import Profile from './components/Profile/Profile';
import Header from './components/Header/Header';
import firebase from './util/firebase';
class App extends Component
{
constructor(){
super();
this.state={
user:''
}
}
componentDidMount(){
firebase.auth().onAuthStateChanged(function(user) {
if (user) {
// User is signed in.
this.setState({
user:user
})
// ...
} else {
// User is signed out.
// ...
}
});
}
render(){
const {user}=this.state;
console.log(user);
return(
<div>
{
user?(
<Router>
<Header/>
<Switch>
<Route exact path="/feed">
<Feed user={user}/>
</Route>
<Route path="/search">
<Search/>
</Route>
<Route path="/profile">
<Profile/>
</Route>
</Switch>
</Router>
):(
<SignUp/>
)
}
</div>
)
}
}
export default App;
Problem: there is no user because react is rendering the component before onAuthStateChanged's changes in your state so when it renders user is null.
Solution: (Probably not the best approach):
show the fall back UI while it's updating the State.
I'm sharing my solution by changing your code.
It worked for me when I faced the same issue.
import React,{Component} from 'react';
import SignUp from './components/Authentication/SignUp';
import {BrowserRouter as Router,Switch,Route,Link} from 'react-router-dom';
import Feed from './components/Feed/Feed';
import Search from './components/SearchUser/Search';
import Profile from './components/Profile/Profile';
import Header from './components/Header/Header';
import firebase from './util/firebase';
class App extends Component
{
constructor(){
super();
this.state={
user:'',
loading: true
}
}
componentDidMount(){
firebase.auth().onAuthStateChanged(function(user) {
if (user) {
// User is signed in.
this.setState({
user:user,
loading: false
})
// ...
} else {
// User is signed out.
// ...
}
});
}
render(){
const {user, loading}=this.state;
console.log(user);
if(loading) return <div>loading...</div>
return(
<div>
{
user?(
<Router>
<Header/>
<Switch>
<Route exact path="/feed">
<Feed user={user}/>
</Route>
<Route path="/search">
<Search/>
</Route>
<Route path="/profile">
<Profile/>
</Route>
</Switch>
</Router>
):(
<SignUp/>
)
}
</div>
)
}
}
The issue is that whenever I try to migrate my routes into another file and import them into parent file(routes.js) then whenever I tried to navigate to those migrated route the whole layout is re-rendered and if I keep migrated routes in the parent route component then the layout is rendered once as expected.
The route component where all the routes are written as below
routes.js
import React, { Component } from 'react';
import {Route, Switch} from 'react-router-dom';
import PrivateRoute from './PrivateRoute';
import MainLayout from '../pages/layouts/MainLayout';
import Login from '../components/Login/Login';
import UserList from "../components/User/UserList";
import UserCreate from '../components/User/UserCreate';
import UserEdit from '../components/User/UserEdit';
import UserSetting from '../components/User/UserSetting';
class Main extends Component {
render() {
return (
<Switch>
<Route exact path='/' component={Login}/>
<PrivateRoute exact path='/user' component={UserList} layout={MainLayout}/>
<PrivateRoute exact path='/user/create' component={UserCreate} layout={MainLayout}/>
<PrivateRoute exact path='/user/edit/:id' component={UserEdit} layout={MainLayout}/>
<PrivateRoute exact path='/user/setting/:id' component={UserSetting} layout={MainLayout}/>
</Switch>
)
}
}
export default Main;
UserRoutes.js
import UserList from "../components/User/UserList";
import UserCreate from '../components/User/UserCreate';
import UserEdit from '../components/User/UserEdit';
import UserSetting from '../components/User/UserSetting';
const UserRoutes = [
{
path : '/user',
component : UserList,
},
{
path : '/user/create',
component : UserCreate,
},
{
path : '/user/edit/:id',
component : UserEdit,
},
{
path : '/user/setting/:id',
component : UserSetting,
},
];
export default UserRoutes;
Now, when I try to import this route in place of the written routes in routes.js component, the above mentioned issue arises.
routes.js
import React, { Component } from 'react';
import {Route, Switch} from 'react-router-dom';
import PrivateRoute from './PrivateRoute';
import MainLayout from '../pages/layouts/MainLayout';
import Login from '../components/Login/Login';
import UserRoutes from './UserRoutes';
class Main extends Component {
render() {
return (
<Switch>
<Route exact path='/' component={Login}/>
{
UserRoutes.map(({ path, component }, key) =>
<PrivateRoute exact layout={MainLayout} path={path} component={component} key={key} />
)
}
</Switch>
)
}
}
export default Main;
PrivateRoute.js
import React from 'react';
import {Redirect, Route, withRouter} from 'react-router-dom';
let state_of_state = localStorage["appState"];
let Auth = {};
if (!state_of_state){
localStorage["appState"] = JSON.stringify({user : {}});
} else {
Auth = JSON.parse(state_of_state);
}
const PrivateRoute = ({ layout: Layout, component: Component, ...rest }) => (
<Route {...rest} render={props => {
const newComponent = Layout ?
<Layout><Component {...rest} {...props} /></Layout>
:
<Component {...props} />;
return (!_.isEmpty(Auth.user)) ? (
newComponent
) : (
<Redirect to={{pathname: '/', state: { from: props.location }}} />
)
}}/>
);
export default PrivateRoute;
I need some guidance where I am going wrong with this.
I am having an issue with my navigation not being updated when a user is being logged in.
So my a portion of my App.js looks like so;
import React from "react";
import { BrowserRouter as Router, Route } from "react-router-dom";
import Home from './views/Home';
import Login from './views/Login';
import Navigation from './components/Navigation';
function App() {
return (
<Router>
<React.Fragment>
<header className="header-global">
<Navigation />
</header>
<Route path="/" exact component={Home} />
<Route path="/login" component={Login} />
<Footer />
</React.Fragment>
</Router>
);
}
export default App;
In my Login.js file, I make a call to my API, check for valid details, if they are valid the response sets a token and also a expires at value into localStorage and then redirects to the homepage;
.then((response) => {
// Set token here
// set expires at here
// redirect to homepage after successful login
this.props.history.push("/");
This all works flawless, but in my Navigation.js file I want to change the link from Login to Logout but after the redirect it doesnt automatically do that .. I have to actually reload the page to see the Login change to Logout.
I have a seperate file called auth.js which has a function called isAuthenticated, this basically just checks that the token is there and valid and returns either true or false based on this.
This is my Navigation.js file (which i have cut down to only display whats needed), but i dont see anything wrong with it and I cant understand why its not automatically changing the value after the redirect.
import React, { Component } from 'react';
import auth from '../auth';
class Navigation extends Component {
constructor(props) {
super(props);
this.state = {
isAuthenticated: false,
};
}
componentDidMount() {
if ( auth.isAuthenticated() ) {
this.setState({ isAuthenticated: true })
}
}
render() {
const { isAuthenticated } = this.state;
return (
{ isAuthenticated ? 'Logout' : 'Login' }
);
}
}
export default Navigation;
I have also tried using the componentWillMount method as well.
The following is the auth.isAuthenticated code (cut down)
isAuthenticated () {
if (this.isExpired()) {
return false
}
if (!this.getToken()) {
return false
}
return true
}
You need to add props history for Navigation:
import React from "react";
import { BrowserRouter as Router, Route } from "react-router-dom";
import Home from './views/Home';
import Login from './views/Login';
import Navigation from './components/Navigation';
function App() {
return (
<Router>
<React.Fragment>
<header className="header-global">
<Navigation />
</header>
<Route path="/" exact component={Home} />
<Route path="/login" component={Login} />
<Footer />
</React.Fragment>
</Router>
);
}
export default App;
and in componentDidMount add this.props.history.push("/"):
import React, { PureComponent } from 'react';
import auth from '../auth';
class Navigation extends PureComponent {
constructor(props) {
super(props);
this.state = {
isAuthenticated: false,
};
}
componentDidMount() {
if ( auth.isAuthenticated() ) {
this.setState({ isAuthenticated: true });
this.props.history.push("/");
}
}
render() {
const { isAuthenticated } = this.state;
return (
{ isAuthenticated ? 'Logout' : 'Login' }
);
}
}
export default withRouter(Navigation);
Example:
I'm trying to redirect the user to the login page when the /logout routes is hit. The auth is working (jwt token is removed, but the app fails to redirect to /login.
Also If I do to / the app crashes as well.
On the Login route I uses withRouter from the react-router-dom package wih this I can access the this.props.history.push('/redirect_to_a_new_path'), but when I try to wrap the App component with withRouter method it crashes as well.
Please help!
Here is the github repo:
App.js
import React, { Component } from "react";
import {
BrowserRouter as Router,
Route,
Switch,
withRouter
} from "react-router-dom";
import MuiThemeProvider from "material-ui/styles/MuiThemeProvider";
import lightBaseTheme from "material-ui/styles/baseThemes/lightBaseTheme";
import getMuiTheme from "material-ui/styles/getMuiTheme";
import injectTapEventPlugin from "react-tap-event-plugin";
// Components
import Navbar from "./components/Navbar";
import HomePage from "./components/HomePage";
import SpotMap from "./components/SpotMap";
import SignUpPage from "./components/containers/SignUpPage";
import LoginPage from "./components/containers/LoginPage";
import DashboardPage from "./components/containers/DashBoardPage";
import NotFound from "./components/NoteFound";
import Auth from "./modules/Auth";
import "./styles/App.css";
injectTapEventPlugin();
const handleLogout = event => {
Auth.deauthenticateUser();
this.props.history.push("/login");
};
const isLoggedIn = event => {
if (Auth.isUserAuthenticated()) {
this.props.history.push(DashboardPage);
} else {
this.props.history.push(HomePage);
}
};
class App extends Component {
render() {
return (
<MuiThemeProvider muiTheme={getMuiTheme(lightBaseTheme)}>
<Router>
<div>
<Navbar />
<Switch>
<Route exact path="/" component={isLoggedIn} />
<Route path="/spotmap" component={SpotMap} />
<Route path="/dashboard" component={DashboardPage} />
<Route path="/signup" component={SignUpPage} />
<Route path="/login" component={LoginPage} />
<Route path="/logout" component={handleLogout} />
<Route component={NotFound} />
</Switch>
</div>
</Router>
</MuiThemeProvider>
);
}
}
export default App;
This is written in React Router v3 and this is what I need to convert to React Router V4. The routes that does not work for me are the "/" and "logout" routes.
import Base from './components/Base.jsx';
import HomePage from './components/HomePage.jsx';
import DashboardPage from './containers/DashboardPage.jsx';
import LoginPage from './containers/LoginPage.jsx';
import SignUpPage from './containers/SignUpPage.jsx';
import Auth from './modules/Auth';
const routes = {
// base component (wrapper for the whole application).
component: Base,
childRoutes: [
{
path: '/',
getComponent: (location, callback) => {
if (Auth.isUserAuthenticated()) {
callback(null, DashboardPage);
} else {
callback(null, HomePage);
}
}
},
{
path: '/login',
component: LoginPage
},
{
path: '/signup',
component: SignUpPage
},
{
path: '/logout',
onEnter: (nextState, replace) => {
Auth.deauthenticateUser();
// change the current URL to /
replace('/');
}
}
]
};
export default routes;
For the / route you should use render like this:
<Route exact path="/" render={() => {
if (Auth.isUserAuthenticated()) {
(<DashboardPage)/>)
} else {
(<HomePage/>)
}
}} />
For your /logout route using <Redirect>
<Route path="/logout" render={() => {
Auth.deauthenticateUser();
return <Redirect to={{ pathname: "/login" }} />;
}}
/>
I post a second solution for the /logout path using withRouter and this.props.history.push('/login').
Point the /logout Route to its own component "Logout"
wrap the Logout component with withRouter method from react-router-dom.
Logout.js
import React, { Component } from "react";
import Auth from "../modules/Auth";
import { withRouter } from "react-router-dom";
class Logout extends Component {
constructor(props) {
super(props);
this.handleLogout = this.handleLogout.bind(this);
}
handleLogout() {
Auth.deauthenticateUser();
this.props.history.push("/login");
}
render() {
return (
<div>
{this.handleLogout()}
</div>
);
}
}
export default withRouter(Logout);
I would use useHistory.
Do:
import { useHistory } from 'react-router-dom';
Instead of:
import { useHistory } from 'react-router';
(Or else the same error message will appear.)