I have two Private Routes in my application but they keep pointing to the same dashboard component.
App.js
function App() {
return (
<Router>
<AuthProvider>
<Header />
<div>
<Switch>
<Route path="/" exact component={Home}/>
<RegistrationRoute path="/signup" component={SignUp} />
<RegistrationRoute path="/login" component={Login} />
<PrivateRoute path="/user/name" component={DetailForm} exact={true}/>
<PrivateRoute path="/dash" component={Dashboard}/>
</Switch>
</div>
<Footer />
</AuthProvider>
</Router>
);
}
PrivateRoute
import React from 'react'
import { Route, Redirect } from 'react-router-dom';
import { useAuth } from '../contexts/AuthContext';
const PrivateRoute = ({ component: Component, ...rest }) => {
const { isAuth, currentUser, loading } = useAuth();
console.log(rest)
return (
<Route
{...rest}
render={props => {
return isAuth ? <Component {...props} /> : <Redirect to="/login" />
}}/>
)
}
RegistrationRoute
import React from 'react'
import { useAuth } from '../contexts/AuthContext';
import { Route, Redirect } from 'react-router-dom';
const RegistrationRoute = ({ component: Component, ...rest }) => {
const { isAuth } = useAuth();
return (
<Route
{...rest}
render={props => !isAuth ? <Component {...props} /> : <Redirect to="/dash" />}>
</Route>
)
}
My login function pushes to "/dash"
My SignUp function is trying to go to "/user/name"
but I keep getting redirected to /dash
Help me please and thank you <3
EDIT: AuthContext
import React, { useContext, useState, useEffect } from 'react'
import { useHistory } from 'react-router-dom';
import axios from 'axios';
import { login, signup, logout, checkAuth } from '../util/AuthRoutes';
const AuthContext = React.createContext();
export const useAuth = () => {
return useContext(AuthContext);
}
export const AuthProvider = ({ children }) => {
const [isAuth, setIsAuth] = useState();
const [currentUser, setCurrentUser] = useState();
const [loading, setLoading] = useState(false);
const [error, setError] = useState('');
const history = useHistory();
const signUp = async (signUpInfo) => {
const result = await signup(signUpInfo);
if(result.data.auth) {
setCurrentUser(result.data.user);
setIsAuth(true);
history.push("/user/name");
}
}
const logIn = async (loginInfo) => {
setLoading(true);
const result = await login(loginInfo);
console.log(result);
if(result.data.auth) {
setLoading(false);
setCurrentUser(result.data.user);
setIsAuth(true);
history.push("/dash");
}
}
const logOut = () => {
setIsAuth(false);
setLoading(true)
axios.get('http://localhost:4000/logout', { withCredentials: true })
.then(res => {
setLoading(false);
console.log(res);
setCurrentUser(null);
history.push("/");
})
.catch(err => {
console.log(err);
})
}
useEffect(() => {
setLoading(true);
axios.get('http://localhost:4000/checkauth', { withCredentials: true })
.then(res => {
if(res.data.auth){
setCurrentUser(res.data.session.user);
setLoading(false);
setIsAuth(true);
console.log(res);
}
}).catch(err => {
console.log(err);
setLoading(false);
history.push("/")
})
}, [isAuth]);
const value = {
currentUser,
setCurrentUser,
signUp,
logIn,
logOut,
error,
loading,
isAuth
}
return (
<AuthContext.Provider value={value}>
{!loading && children}
</AuthContext.Provider>
)
}
export default AuthContext
Related
I have a react app and I would like the user to be redirected to the '/dashboard' when logged in and not the home route '/'. I have accomplished this with the '/login' route, but cannot get it to do the same for the '/' home route.
Any help or insight would be appreciated
occasionally when playing around I get the infinite loop error from React
import React, { useEffect, useState } from "react";
import { useSelector, useDispatch } from "react-redux";
import { Auth } from "aws-amplify";
import {
BrowserRouter as Router,
Switch,
Route,
Link,
Redirect,
} from "react-router-dom";
import { Provider } from "react-redux";
import ProtectedRoute from "./Utils/ProtectedRoute";
import PublicRoutes from "./Utils/PublicRoute";
import { AuthState, onAuthUIStateChange } from "#aws-amplify/ui-components";
import store from "./store";
import { userLogIn, userLogOut } from "./Actions/userActions";
import NavBar from "./Components/NavBar";
import Home from "./Screens/Home";
import AmplifySignUp from "./Components/AmplifyLogIn";
import Dashboard from "./Screens/Dashboard";
const Routes = (props) => {
const [authState, setAuthState] = useState();
const [user, setUser] = useState();
const [userName, setUserName] = useState();
const dispatch = useDispatch();
const userState = useSelector((s) => s.user);
useEffect(() => {
return onAuthUIStateChange((nextAuthState, authData) => {
setAuthState(nextAuthState);
setUser(authData);
});
});
useEffect(() => {
if (AuthState.SignedIn && user) {
dispatch(userLogIn(user.attributes.email));
}
}, [user]);
useEffect(() => {
if (authState !== AuthState.SignedIn) {
dispatch(userLogOut());
}
}, [user]);
// check auth
const isAuth = async () => {
try {
const status = await Auth.currentAuthenticatedUser();
return status.username;
} catch (err) {}
};
// Auth.currentAuthenticatedUser()
// .then((user) => {
// console.log(user.username)
// setUserName(prevState => {
// if(prevState !== user.username){
// return user.username
// }else {return prevState}
// }
// )
// })
// .catch((err) => console.log(err));
return (
<Router>
<NavBar />
<Switch>
<Route exact path="/">
{userName ? <Redirect from='/' to="/dashboard" /> : <Home />}
</Route>
<Route path="/login">
{authState === AuthState.SignedIn && user ? (
<Redirect to="/dashboard" />
) : (
<AmplifySignUp />
)}
</Route>
<ProtectedRoute path="/dashboard" user={user}>
<Dashboard />
</ProtectedRoute>
</Switch>
</Router>
);
};
export default Routes;
ProtectedRoutes.js
import React from "react";
import { Route, Redirect } from "react-router-dom";
import { useSelector, useDispatch } from "react-redux";
const ProtectedRoutes = ({user, children, ...rest }) => {
console.log(user && user)
return (
<Route
{...rest}
render={() => {
return user ? (
children
) : (
<Redirect
to='/'
/>
);
}}
/>
);
};
export default ProtectedRoutes;
Issue
On this line...
{userName ? <Redirect from='/' to="/dashboard" /> : <Home />}
You're not updating the username state with the currently authenticated user, hence the dashboard redirect never happens.
Possible Solution
Add a function to get the currently authenticated user
Add a useEffect hook to update the username state with the returned user
Code
const getAuthenticatedUser = async () => {
try {
const user = await Auth.currentAuthenticatedUser();
return {
user,
};
} catch (error) {
return { error };
}
};
useEffect(() => {
const { user, error } = await getAuthenticatedUser();
if (!error) {
setUsername(user.username);
}
}, []);
I'm using reach router for my routes. I was able to protect the dashboard and expose the login page and redirect if the user is not logged in but now if I enter a url it will do a quick redirecto to login and then to home instead of the page actually entered.
I noticed because of the useEffect to fetch the user the component renders twice: 1 without user (redirects to login) the other one with user and redirects to home.
Routes file
const AdminRoutes = () => {
return (
<Router>
<MainLayout path="/admin">
<HomePage path="/" />
<CarTransfer path="/cartransfers">
<CarTranserList path="/list" />
<CarTransferCreate path="/new" />
</CarTransfer>
<User path="/users">
<UserList path="/list" />
<UserCreate path="/new" />
</User>
</MainLayout>
<LoginPage path="/login" />
</Router>
);
};
Layout file
import { useState, useEffect } from "react";
import { Redirect, useNavigate } from "#reach/router";
import { Layout } from "antd";
import SiderMenu from "./SiderMenu";
import LayoutBanner from "./LayoutBanner";
import { useSelector, useDispatch } from "react-redux";
import {
userSelector,
fetchUserBytoken,
clearState,
} from "../../features/authSlice";
const { Content } = Layout;
const MainLayout = ({ children }) => {
const user = useSelector(userSelector);
const [collapsed, setCollapsed] = useState(false);
const navigate = useNavigate();
const dispatch = useDispatch();
const { isFetching, isError } = useSelector(userSelector);
useEffect(() => {
dispatch(
fetchUserBytoken({
token: localStorage.getItem("token"),
id: localStorage.getItem("id"),
})
);
}, []);
useEffect(() => {
if (isError) {
dispatch(clearState());
navigate("/login");
}
}, [isError]);
const handleOnCollapse = () => {
setCollapsed((prevState) => !prevState);
};
if (isFetching) {
return <div>Loading</div>;
}
if (user.id === "") {
return <Redirect noThrow to="/login" />;
}
return (
<Layout>
<SiderMenu collapsed={collapsed} handleOnCollapse={handleOnCollapse} />
<Layout>
<LayoutBanner
collapsed={collapsed}
handleOnCollapse={handleOnCollapse}
/>
<Content>{children}</Content>
</Layout>
</Layout>
);
};
export default MainLayout;
The second question would be how to get to the same page you were before the login redirect after login.
Thanks
I have a PrivateRoute component that protects any route requiring a valid login in the app. I have a route called Spec.tsx that is called from App.tsx. PrivateRoute will spit you out on the Login page if you're not logged in, which is great. And Login will send you directly to the Home page if you are logged in. Also great.
Right now, I'm caught in a loop where when I go to Spec, the app thinks I'm not logged in, and sends me to Login, which does think I'm logged in, and so sends me back to Home. currentUser is always calculated the same was, like const {currentUser} = useContext(AuthContext);
When I log the currentUser in PrivateRoute as I navigate to /spec/:id it says null once, and then gives the correct answer 3 more times. I suspect the null causes me to get booted to Login and then currentUser must be assigned correctly as I'm sent back to Home immediately. I don't even ever get to Spec.tsx, nothing I try to log there gets logged. Can anyone point out what I'm doing wrong? Thanks
//In App.tsx
<AuthProvider>
<Router history={history}>
<Navbar />
<Switch>
<PrivateRoute exact path="/" component={Home} />
<PrivateRoute exact path="/spec/:id" render={() => (
<Spec isEdit={true}/>
)}/>
<Route exact path="/login" component={Login} />
<Route exact path="/signup" component={Signup} />
</Switch>
</Router>
</AuthProvider>
//PrivateRoute.tsx
import React, { useContext } from "react";
import { Route, Redirect } from "react-router-dom";
import { AuthContext } from "../Util/Auth";
const PrivateRoute = ({ component: RouteComponent, ...rest }: any) => {
const {currentUser} = useContext(AuthContext);
console.log(currentUser)
return (
<Route
{...rest}
render={(routeProps: any) =>
!!currentUser ? (
<RouteComponent {...routeProps} />
) : (
<Redirect to={"/login"} />
)
}
/>
);
};
export default PrivateRoute
//Login.tsx
const Login = ({ history }: any) => {
const { currentUser } = useContext(AuthContext);
if (currentUser) {
return <Redirect to="/" />;
}
return (
//My JSX
)
My Auth provider is below, which handles all changes to currentUser
//Auth.tsx
import React, { useEffect, useState } from "react";
import { createNewUser } from "../Models/User";
import app from "./firebase";
const initialUser: any = null;
export const AuthContext = React.createContext(initialUser);
export const AuthProvider: React.FC = ({ children }) => {
const [currentUser, setCurrentUser] = useState<any>(null);
const [currentDBUser, setCurrentDBUser] = useState<any>(null);
const [pending, setPending] = useState<boolean>(true);
const [pending2, setPending2] = useState<boolean>(true);
useEffect(() => {
//Auth user
app.auth().onAuthStateChanged((user) => {
setPending(false)
setCurrentUser(user);
});
//Grab database data for user
async function fetchData() {
if (currentUser && !currentDBUser && pending2) {
const newUser: User = {fbUser: currentUser.uid, email: currentUser.email}
const userDBObject = await createNewUser(newUser);
if (userDBObject) {
setCurrentDBUser(userDBObject);
setPending2(false);
}
}
}
if (pending2) {
fetchData();
}
}, [currentUser, currentDBUser, pending2]);
if (pending) {
return <>Loading...</>
}
return (
<AuthContext.Provider value= {{ currentUser, currentDBUser }}>
{ children }
</AuthContext.Provider>
);
};
I want to create a protected route that only authenticated users can access. It works BUT when im refreshing the page the context is undefined and redirects the user to the landing page. I dont really see why this is happening.
Console log after refreshing page
App.js
import React, { useState, useEffect } from "react";
import { BrowserRouter, Switch, Route } from "react-router-dom";
import Axios from "axios";
import Home from "./components/pages/Home";
import LandingPage from "./components/pages/LandingPage";
import { ProtectedRoute } from "./components/auth/ProtectedRoute";
import UserContext from "./context/UserContext";
import "./style.css";
export default function App() {
const [userData, setUserData] = useState({
token: undefined,
user: undefined,
});
const checkLoggedIn = async () => {
let token = localStorage.getItem("auth-token");
if (token === null) {
localStorage.setItem("auth-token", "");
token = "";
}
const tokenRes = await Axios.post(
"http://localhost:5000/users/tokenIsValid",
null,
{ headers: { "x-auth-token": token } }
);
if (tokenRes.data) {
const userRes = await Axios.get("http://localhost:5000/users/", {
headers: { "x-auth-token": token },
});
setUserData({
token,
user: userRes.data,
});
}
};
useEffect(() => {
console.log('useEffect called');
checkLoggedIn();
}, []);
return (
<>
<BrowserRouter>
<UserContext.Provider value={{ userData, setUserData }}>
<Switch>
<Route exact path="/" component={LandingPage} />
<ProtectedRoute exact path="/home" component={Home} />
</Switch>
</UserContext.Provider>
</BrowserRouter>
</>
);
}
ProtectedRoute.js
import React, { useContext } from "react";
import UserContext from "../../context/UserContext";
import { Route, Redirect } from "react-router-dom";
export const ProtectedRoute = ({
component: Component,
...rest
}) => {
const { userData } = useContext(UserContext);
return (
<Route
{...rest}
render={props => {
console.log("USERDATA " + userData.user)
console.log("TOKEN " + localStorage.getItem("auth-token"))
if (userData.user) {
return <Component {...props} />;
} else {
return (
<Redirect
to={{
pathname: "/",
state: {
from: props.location
}
}}
/>
);
}
}}
/>
);
};
EDIT
After a while i figured out the problem, which is explained in this question REACT - Check authentication before render APP
After a while i figured out the problem, which is explained in this question REACT - Check authentication before render APP
This is my solution, which works fine.
App.js
export default function App() {
const [userData, setUserData] = useState({
isLoggedIn: false,
isLoading: true,
token: undefined,
user: undefined
});
const checkLoggedIn = async () => {
const tokenRes = await validateToken();
if (tokenRes.data) {
const userRes = await getUser();
let token = localStorage.getItem("auth-token");
setUserData({
token,
user: userRes.data,
isLoggedIn: true,
isLoading: false
});
}
};
useEffect(() => {
console.log('useEffect called');
checkLoggedIn();
}, []);
console.log("APPPPPP")
return (
<>
<BrowserRouter>
<UserContext.Provider value={{ userData, setUserData }}>
<Switch>
<Route exact path="/" component={LandingPage} />
<ProtectedRoute exact path="/home" component={Home} />
</Switch>
</UserContext.Provider>
</BrowserRouter>
</>
);
}
ProtectedRoute.js
import React, {useContext} from "react";
import { Route, Redirect } from "react-router-dom";
import UserContext from "../../context/UserContext";
const ProtectedRoute = ({ component: Comp, path, ...rest }) => {
const { userData } = useContext(UserContext);
console.log(userData.isLoggedIn);
return (
<Route path={path} {...rest}
render={props => {
return userData.isLoggedIn ? (<Comp {...props} />) : (userData.isLoading ? 'Loading...' : <Redirect to="/" />)
}}
/>
);
};
export default ProtectedRoute;
I am creating a private route to be accessed only when the firebase listener verifies that the user is logged in, but I cannot access this route the way I am doing.
const PrivateRoute = ({ component: Component, ...rest }) => {
let autenticado = false;
firebase.auth().onAuthStateChanged((user) => {
if (user) {
autenticado = true;
} else {
autenticado = false;
}
});
return (
<Route
{...rest}
render={(props) =>
autenticado ? (
<Component {...props} />
) : (
<Redirect to={{ pathname: "/", state: { from: props.location } }} />
)
}
/>
);
};
const Routes = () => (
<BrowserRouter>
<Switch>
<Route path="/" exact component={Login} />
<PrivateRoute path="/home" component={Home} />
</Switch>
</BrowserRouter>
);
export default Routes;
Alvaro's solution is good, but Robert Terrell brought up a good point:
if you refresh your app, the private route kicks you back to the login page because it takes a few moments for the currentUser to re register
.
To fix this, id wait for the authcontext to load before displaying the site on line 24 in AuthContext.tsx:
import React, { useEffect, useState } from "react";
import {auth, firebase} from "../firebaseSetup";
type ContextProps = {
user: firebase.User | null;
};
export const AuthContext = React.createContext<Partial<ContextProps>>({});
export const AuthProvider = ({ children }: any) => {
const [user, setUser] = useState(null as firebase.User | null);
const [loading, setLoading] = useState(true);
useEffect(() => {
auth.onAuthStateChanged((user: any) => {
setUser(user);
setLoading(false);
});
}, []);
return (
<AuthContext.Provider
value={{user}}>
{!loading && children}
</AuthContext.Provider>
);
}
It's hard to tell where your code is going wrong without seeing other components. I do basically the same approach when I set up firebase auth and set up private routes. I bring my context into my PrivateRoute component like so:
import React, { useContext } from "react";
import { Route, Redirect } from "react-router-dom";
import { AuthContext } from "./Auth";
const PrivateRoute = ({ component: RouteComponent, ...rest }) => {
const {currentUser} = useContext(AuthContext);
return (
<Route
{...rest}
render={routeProps =>
!!currentUser ? (
<RouteComponent {...routeProps} />
) : (
<Redirect to={"/admin/login"} />
)
}
/>
);
};
My Auth.js file is where I create my context like so:
import React, { useEffect, useState } from "react";
import app from "./base.js";
export const AuthContext = React.createContext();
export const AuthProvider = ({ children }) => {
const [currentUser, setCurrentUser] = useState(null);
useEffect(() => {
app.auth().onAuthStateChanged(setCurrentUser);
}, []);
return (
<AuthContext.Provider
value={{
currentUser
}}
>
{children}
</AuthContext.Provider>
);
};
I initiate my firebase instance in a file called base.js that looks like so :
import * as firebase from "firebase/app";
import "firebase/auth";
console.log(process.env.REACT_APP_FIREBASE_DATABASE)
const app = firebase.initializeApp({
apiKey: process.env.REACT_APP_FIREBASE_KEY,
authDomain: process.env.REACT_APP_FIREBASE_DOMAIN,
databaseURL: process.env.REACT_APP_FIREBASE_DATABASE,
projectId: process.env.REACT_APP_FIREBASE_PROJECT_ID,
storageBucket: process.env.REACT_APP_FIREBASE_STORAGE_BUCKET,
messagingSenderId: process.env.REACT_APP_FIREBASE_SENDER_ID
});
export default app;
In your routes you would then add the AuthProvider
const Routes = () => (
<BrowserRouter>
<Switch>
<AuthProvider>
<Route path="/" exact component={Login} />
<PrivateRoute path="/home" component={Home} />
</AuthProvider>
</Switch>
</BrowserRouter>
);
export default Routes;