I am learning firebase. I have a button log out. When I am clicking on the button it is not navigating to login page.
I have set routing with user.emailVerified value.
Nav.jsx
import { getAuth, signOut } from 'firebase/auth';
import React from 'react';
import { useDispatch } from 'react-redux';
import { Link, useNavigate } from 'react-router-dom';
import { setLogOut } from '../redux/slice';
const Nav = () => {
const navigate = useNavigate();
const auth = getAuth();
const dispatch = useDispatch()
const logOutHandler = () => {
signOut(auth)
.then(() => {
dispatch(setLogOut())
navigate('/login', { replace: true });
})
.catch(error => {
console.log(error);
});
};
return (
<nav className="navbar navbar-expand-lg bg-light ">
<div className="container">
<Link className="navbar-brand" to="/">
Navbar
</Link>
<Link className="nav-link" to="/">
Home
</Link>
<button className="btn btn-outline-secondary" onClick={logOutHandler}>
Log Out
</button>
</div>
</nav>
);
};
export default Nav;
App.jsx
import React, { useState } from 'react';
import 'bootstrap/dist/css/bootstrap.css';
import 'bootstrap/dist/js/bootstrap.min.js';
import 'bootstrap-icons/font/bootstrap-icons.css';
import { Routes, Route, Navigate } from 'react-router-dom';
import Nav from './components/Nav';
import Home from './pages/Home';
import Login from './pages/Login';
import SignUp from './pages/SignUp';
import EmailVerify from './pages/EmailVerify';
import { getAuth, onAuthStateChanged } from 'firebase/auth';
const App = () => {
const [verified, setverified] = useState();
// get user information
const auth = getAuth();
onAuthStateChanged(auth, user => {
if (user) {
setverified(user.emailVerified);
} else {
// User is signed out
// ...
}
});
let routes;
if (verified) {
routes = (
<>
<Nav />
<Routes>
<Route path="/" element={<Home />} />
<Route path="*" element={<Navigate to="/" replace />} />
</Routes>
</>
);
} else {
routes = (
<>
<Routes>
<Route path="/login" element={<Login />} />
<Route path="/signup" element={<SignUp />} />
<Route path="/email-verify" element={<EmailVerify />} />
<Route path="*" element={<Navigate to="/login" replace />} />
</Routes>
</>
);
}
return <>{routes}</>;
};
export default App;
redux
import { createSlice } from '#reduxjs/toolkit';
import { getAuth, onAuthStateChanged } from 'firebase/auth';
const initialState = {
email: JSON.parse(localStorage.getItem('email')),
isLoggedIn: JSON.parse(localStorage.getItem('loggedInStatus')),
};
export const slice = createSlice({
name: 'slice',
initialState,
reducers: {
setLogInValue: (state, action) => {
localStorage.setItem('email', JSON.stringify(action.payload));
localStorage.setItem('loggedInStatus', JSON.stringify(true));
state.email = action.payload;
state.isLoggedIn = true;
},
setLogOut: state => {
localStorage.setItem('email', null);
localStorage.setItem('loggedInStatus', false);
state.email = null;
state.isLoggedIn = false;
},
},
});
export const { setLogInValue, setLogOut } = slice.actions;
export default slice.reducer;
I am wanting to go login page when I click on log out button because when I am clicking on logout it is user is null then. But why not navigating to login page? How can I do that?
I have added setverified(false) on else condition of the onAuthStateChanged on App.jsx and it is working as I wanted.
Updated portion:
onAuthStateChanged(auth, user => {
if (user) {
setverified(user.emailVerified);
} else {
setverified(false); // updated line
}
});
Related
An unauthenticated user can still see the "/account" page for a few seconds I mean there is still a delay of a few seconds before the unauthenticated user is completely redirected to another page. even though I have used ProtectedRoute, how can I make unauthenticated users unable to see the "/account" page at all without delay? i use react-router-dom 6.6.1 and firebase auth
here is for the app.js
import { ThemeProvider } from '#mui/material/styles';
import React from 'react';
import { BrowserRouter as Router, Route, Routes, Navigate} from 'react-router-dom'
import Home from './components/Home'
import Account from './components/Account';
import Signin from './components/Home/Signin';
import theme from './theme';
import { AuthContextProvider } from './components/Context/AuthContext';
import ProtectedRoute from './components/ProtectedRoute';
function App() {
return (
<ThemeProvider theme={theme}>
<AuthContextProvider>
<Router>
<Routes>
<Route path='/' element={<Home />} />
<Route path='/signin' element={<Signin />} />
<Route path='/account' element={<ProtectedRoute><Account /></ProtectedRoute>} />
</Routes>
</Router>
</AuthContextProvider>
</ThemeProvider>
)
}
export default App
and this is for the ProtectedRoute.js
import React from 'react';
import { Navigate } from 'react-router-dom';
import { UserAuth } from './Context/AuthContext';
const ProtectedRoute = ({ children }) => {
const { user } = UserAuth();
if (!user) {
return <Navigate to='/' />;
}
return children;
};
export default ProtectedRoute;
this if for the UserAuth :
import { createContext, useContext, useEffect, useState } from 'react';
import {
createUserWithEmailAndPassword,
signInWithEmailAndPassword,
onAuthStateChanged,
} from 'firebase/auth';
import { auth } from '../../firebase';
const UserContext = createContext();
export const AuthContextProvider = ({ children }) => {
const [user, setUser] = useState({});
const createUser = (email, password) => {
return createUserWithEmailAndPassword(auth, email, password);
};
const signIn = (email, password) => {
return signInWithEmailAndPassword(auth, email, password)
}
useEffect(() => {
const unsubscribe = onAuthStateChanged(auth, (currentUser) => {
setUser(currentUser);
});
return () => {
unsubscribe();
};
}, []);
return (
<UserContext.Provider value={{ createUser, user, signIn }}>
{children}
</UserContext.Provider>
);
};
export const UserAuth = () => {
return useContext(UserContext);
};
I want to redirect unauthenticated users to the main page without delay
it’s probably due to the fact that your guard is validating off of !user however your initial auth state has user as {}. In this case an empty object will validate as truthy.
You could either set the initial state of user as null or you could update th guard validation to check for a value on user like if (!user.id) …
Render Protected and Base routes conditional so unauthenticated user never have access of Protected routes.
Use the following code.
Always make separate file for routing for better code readability and Separation of concerns.
BaseRoutes.js
import React from 'react'
import { Route } from 'react-router-dom'
import Home from './components/Home'
import Signin from './components/Home/Signin';
const BaseRoutes = (
<>
<Route path='/' element={<Home />} />
<Route path='/signin' element={<Signin />} />
</>
)
export default BaseRoutes
ProtectedRoutes.js
import React from 'react';
import { Route } from 'react-router-dom';
import Account from './components/Account';
const ProtectedRoutes = () => {
return (
<>
<Route path='/account' element={<Account />} />
</>
)
};
export default ProtectedRoutes;
Routes.js
import React from 'react'
import { Navigate, Route, Routes, BrowserRouter } from 'react-router-dom'
import { UserAuth } from './Context/AuthContext';
import ProtectedRoutes from './components/ProtectedRoutes';
import BaseRoutes from './components/BaseRoutes';
const Router = () => {
const { user } = UserAuth();
const isUser = Boolean(user)
const redirectUrl = isUser ? '/account' : '/signin'
return (
<BrowserRouter>
<Routes>
{isUser ? <BaseRoutes /> : <ProtectedRoutes />}
<Route
path="*"
element={<Navigate to={redirectUrl} />}
/>
</Routes>
</BrowserRouter>
)
}
export default Router
app.js
import React from 'react';
import { AuthContextProvider } from './components/Context/AuthContext';
import { ThemeProvider } from '#mui/material/styles';
import theme from './theme';
import Router from './routes'
function App() {
return (
<ThemeProvider theme={theme}>
<AuthContextProvider>
<Router />
</AuthContextProvider>
</ThemeProvider>
)
}
export default App
my app.jsx
import axios from "axios";
import { createContext, useState } from "react";
import { BrowserRouter, Routes, Route } from "react-router-dom";
import NavBar from "./components/Navbar";
import ProtectedRoute from "./components/ProtectedRoute";
import Home from "./pages/Home";
import Login from "./pages/Login";
import MyBlogs from "./pages/MyBlogs";
import Profile from "./pages/Profile";
import Signup from "./pages/Signup";
export const AuthContext = createContext();
const App = () => {
const [loggedIn, setLoggedIn] = useState(
Boolean(sessionStorage.getItem("loggedIn"))
);
const VUserData = sessionStorage.getItem("userData");
const [userData, setUserData] = useState(
VUserData ? JSON.parse(VUserData) : null
);
function getUserData() {
return axios
.get("/api/user")
.then((res) => {
return Promise.resolve(res.data);
})
.catch((err) => {
return Promise.reject(err.response);
});
}
return (
<BrowserRouter>
<AuthContext.Provider
value={{
loggedIn,
setLoggedIn,
userData,
setUserData,
getUserData,
}}
>
<Routes>
<Route path="/" element={<NavBar />}>
<Route index element={<Home />} />
<Route path="login" element={<Login />} />
<Route path="signup" element={<Signup />} />
<Route
path="profile"
element={
<ProtectedRoute>
<Profile />
</ProtectedRoute>
}
/>
<Route
path="my-blogs"
element={
<ProtectedRoute>
<MyBlogs />
</ProtectedRoute>
}
/>
</Route>
</Routes>
</AuthContext.Provider>
</BrowserRouter>
);
};
export default App;
my ProtectedRoute.jsx
import { useContext } from "react";
import { Navigate, useLocation } from "react-router-dom";
import { AuthContext } from "../app";
const ProtectedRoute = ({ children, ...rest }) => {
const { loggedIn, getUserData } = useContext(AuthContext);
const location = useLocation();
if (loggedIn) {
getUserData()
.then((res) => {
return children;
})
.catch((res) => {
return (
<Navigate
to="/login"
replace
state={{ path: location.pathname }}
/>
);
});
} else {
return (
<Navigate to="/login" replace state={{ path: location.pathname }} />
);
}
};
export default ProtectedRoute;
my home.jsx
import React from "react";
import { Outlet } from "react-router-dom";
import NavBar from "../components/Navbar";
const Home = () => {
return (
<React.Fragment>
<NavBar />
<Outlet />
</React.Fragment>
);
};
export default Home;
My problem is after successful login, the profile page not rendering. It's just a blank white page but if I replacegetUserData() and the whole promise resolve with just return children; it's in ProtectedRoute.jsx it's working fine why is that?
how could I check whether the user is logged or not by requesting to the backend in ProtectedRoute.jsx
I am trying to make it so that when a user clicks on a certain Link (Upload Component), it will redirect them to to the login component and am not sure how to accomplish this task in React. I was directed to another answered question, but it hasn't helped me as I am still confused on what I need to do with my own set up. I understand I need to make my own protected route (maybe), but I saw others accessing useContext and I do not have any file with context. I am using Version 6 in react dom. I am also using React router and redux in my project so I know I need to access the state somehow, just not sure how to wrap my mind around it so if anyone could help, I would appreciate it. The user is being stored in local storage with JWT authentication.
App.js:
import React, {useState, useEffect, useCallback} from 'react';
import {useDispatch, useSelector} from 'react-redux';
import {BrowserRouter as Router, Routes, Route} from 'react-router-dom'; //Switch was replaced by Routes in react-router-dom v6
import './App.css';
import Navbar from './components/Navbar';
import Footer from './components/Footer';
import Home from './components/pages/Home';
import Uploads from './components/pages/Uploads';
import Account from './components/pages/Account';
import Login from './components/pages/Login';
import LogOut from './components/Logout';
import Register from './components/pages/Register';
import Profile from './components/pages/Profile';
import EditProfile from './components/pages/EditProfile';
import BoardAdmin from './components/pages/BoardAdmin';
import BoardModerator from './components/pages/BoardModerator';
import BoardUser from './components/pages/BoardUser';
import {logout} from './slices/auth';
import EventBus from './common/EventBus';
const App = () => {
const [showModeratorBoard, setShowModeratorBoard] = useState(false);
const [showAdminBoard, setShowAdminBoard] = useState(false);
const {user: currentUser} = useSelector((state) => state.auth);
const dispatch = useDispatch();
useEffect(() => {
if (currentUser) {
setShowModeratorBoard(currentUser.roles.includes('ROLE_MODERATOR'));
setShowAdminBoard(currentUser.roles.includes('ROLE_ADMIN'));
} else {
setShowModeratorBoard(false);
setShowAdminBoard(false);
}
}, [currentUser]);
return (
<>
<Router>
<Navbar />
<Routes>
<Route path='/' element={<Home />} />
<Route path='/create/upload' element={<Uploads />} />
<Route path='/my-account' element={<Account />} />
<Route path='/login' element={<Login />} />
<Route path='/logout' element={<LogOut />} />
<Route path='/register' element={<Register />} />
<Route path='/profile' element={<Profile />} />
<Route path='/profile/edit' element={<EditProfile />} />
<Route path='/user' element={<BoardUser />} />
<Route path='/mod' element={<BoardModerator />} />
<Route path='/admin' element={<BoardAdmin />} />
</Routes>
<Footer />
</Router>
</>
);
};
export default App;
Auth.js:
import {createSlice, createAsyncThunk} from '#reduxjs/toolkit';
import {setMessage} from './messages';
import AuthService from '../services/auth.service';
const user = JSON.parse(localStorage.getItem('user'));
export const register = createAsyncThunk(
'auth/register',
async ({username, email, password}, thunkAPI) => {
try {
const response = await AuthService.register(username, email, password);
thunkAPI.dispatch(setMessage(response.data.message));
return response.data;
} catch (error) {
const message =
(error.response &&
error.response.data &&
error.response.data.message) ||
error.message ||
error.toString();
thunkAPI.dispatch(setMessage(message));
return thunkAPI.rejectWithValue();
}
}
);
export const login = createAsyncThunk(
'auth/login',
async ({username, password}, thunkAPI) => {
try {
const data = await AuthService.login(username, password);
return {user: data};
} catch (error) {
const message =
(error.response &&
error.response.data &&
error.response.data.message) ||
error.message ||
error.toString();
thunkAPI.dispatch(setMessage(message));
return thunkAPI.rejectWithValue();
}
}
);
export const logout = createAsyncThunk('auth/logout', async () => {
await AuthService.logout();
});
const initialState = user
? {isLoggedIn: true, user}
: {isLoggedIn: false, user: null};
const authSlice = createSlice({
name: 'auth',
initialState,
extraReducers: {
[register.fulfilled]: (state, action) => {
state.isLoggedIn = false;
},
[register.rejected]: (state, action) => {
state.isLoggedIn = false;
},
[login.fulfilled]: (state, action) => {
state.isLoggedIn = true;
state.user = action.payload.user;
},
[login.rejected]: (state, action) => {
state.isLoggedIn = false;
state.user = null;
},
[logout.fulfilled]: (state, action) => {
state.isLoggedIn = false;
state.user = null;
},
},
});
const {reducer} = authSlice;
export default reducer;
It looks like adding the if statement in my upload file is making it so that a logged in user can access the h1 element that is in the file, but not an unauthenticated user. So its working to an extent, just not redirecting back to login.
Upload.jsx:
import React from 'react';
import {Link} from 'react-router-dom';
import {useSelector} from 'react-redux';
function Uploads() {
const {user: currentUser} = useSelector((state) => state.auth);
if (!currentUser) {
return <Link to='/login' />;
}
return (
<>
<h1 className='page'>UPLOADS</h1>
</>
);
}
export default Uploads;
Rendering the Link doesn't imperatively navigate, use the Navigation component.
Example:
import React, { useEffect } from 'react';
import { Navigate } from 'react-router-dom';
import { useSelector } from 'react-redux';
function Uploads() {
const { user: currentUser } = useSelector((state) => state.auth);
if (!currentUser) {
return <Navigate to='/login' replace />;
}
return (
<>
<h1 className='page'>UPLOADS</h1>
</>
);
}
export default Uploads;
Typically you would protect the route instead. If you wanted to do that:
import { Navigate, Outlet } from 'react-router-dom';
import { useSelector } from 'react-redux';
const PrivateRoutes = () => {
const { user: currentUser } = useSelector((state) => state.auth);
return currentUser
? <Outlet />
: <Navigate to="/login" replace />;
}
...
<Routes>
<Route path='/' element={<Home />} />
<Route element={<PrivateRoutes />}>
<Route path='/create/upload' element={<Uploads />} />
... any other protected routes ...
</Route>
... other unprotected routes ...
</Routes>
Recently, I am facing issue with Custom Route with latest react-router-dom version 6 in my code.
I have created custom route for authorized if user is not login then it will redirect to sign in page. It was working with previous react router version but now it's got broke with latest one.
I am posting the code below.
Getting below error message as well.
[UserRoute] is not a component. All component children of must be a Route or <React.Fragment>
LoadingToRedirect.js
import React, { useState, useEffect } from "react";
import { useNavigate } from "react-router-dom";
const LoadingToRedirect = () => {
const [count, setCount] = useState(5);
const navigate = useNavigate();
useEffect(() => {
const interval = setInterval(() => {
setCount((currentCount) => --currentCount);
}, 1000);
count === 0 && navigate("/login");
return () => clearInterval(interval);
}, [count, navigate]);
return (
<div>
<p>Redirecting you in {count} seconds</p>
</div>
);
};
export default LoadingToRedirect;
UserRoute.js
import React from "react";
import { Route, Routes } from "react-router-dom";
import { useSelector } from "react-redux";
import LoadingToRedirect from "./LoadingToRedirect";
const UserRoute = ({ children, ...rest }) => {
const { currentUser } = useSelector((state) => state.user);
return currentUser ? <Route {...rest} /> : <LoadingToRedirect />;
};
export default UserRoute;
App.js
import React, { useEffect, Fragment } from "react";
import "./App.css";
import { BrowserRouter, Routes, Route } from "react-router-dom";
import Home from "./pages/Home";
import Login from "./pages/Login";
import Register from "./pages/Register";
import UserRoute from "./components/UserRoute";
import { useDispatch } from "react-redux";
import { auth } from "./firebase";
import { setUser } from "./redux/actions";
import Header from "./components/Header";
import AddEdit from "./pages/AddEdit";
function App() {
const dispatch = useDispatch();
useEffect(() => {
auth.onAuthStateChanged((authUser) => {
if (authUser) {
dispatch(setUser(authUser));
} else {
dispatch(setUser(null));
}
});
}, [dispatch]);
return (
<BrowserRouter>
<div className="App">
<Header />
<ToastContainer position="top-center" />
<Fragment>
<Routes>
<UserRoute path="/" element={<Home />} />
<Route path="/login" element={<Login />} />
<Route path="/register" element={<Register />} />
<UserRoute path="/addContact" element={<AddEdit />} />
</Routes>
</Fragment>
</div>
</BrowserRouter>
);
}
export default App;
In react-router v6, the Routes component allows only Route children.
In UserRoute.js if there's no user you are returning LoadingToRedirect:
return currentUser ? <Route {...rest} /> : <LoadingToRedirect />;
You can try changing this to:
const UserRoute = ({ children, path, element }) => {
const { currentUser } = useSelector((state) => state.user);
return <Route path={path} element={ currentUser ? element : <LoadingToRedirect />} />;
};
I want to implement a case where when users are logged in that they need to able to access the AppStack and the AuthStack. And if they not logged in, only the AuthStack will be accessible. Also when they try to access the AppStack they should get redirected. In react native I could handle this with a AuthStack and if something changed the user status he could see the one stack or the other one. But I don't know how to do it with reactjs
AuthNavigator
import React, { useState, useEffect } from 'react';
import app from './firebase';
import AuthStack from './stacks/AuthStack';
import AppStack from './stacks/AppStack';
function AuthNavigator() {
const [initializing, setInitializating] = useState(true);
const [user, setUser] = useState(true);
function onAuthStateChanged(result) {
setUser(result);
if (initializing) setInitializating(false);
if (result) {
app
.auth()
.currentUser.getIdToken(/* force Refresh */ true)
.then((idToken) => {
result.idToken = idToken;
setUser(result);
})
.catch((error) => {
console.log(error.message);
});
}
}
useEffect(() => {
const authSubscriber = app.auth().onAuthStateChanged(onAuthStateChanged);
return authSubscriber;
});
if (initializing) {
return null;
}
return user ? <AppStack user={user} /> : <AuthStack user={user} />;
}
export default AuthNavigator;
AppStack here i tried it like if there is a user then you can render the appStack else you get redirected to the authstack.
import React from 'react';
import { BrowserRouter as Router, Switch, Route } from 'react-router-dom';
import { Redirect } from 'react-router';
import Overview from '../pages/app/Overview';
import Sidebar from '../components/Sidebar/Sidebar';
function AppStack({ user }) {
return this.props.user ? (
<Router>
<Switch>
<Sidebar user={user} />
<Route path="/app-melior/overview" exact component={Overview}></Route>
</Switch>
</Router>
) : (
<Redirect path="/auth/signin"></Redirect>
);
}
export default AppStack;
AuthStack
import React from 'react';
import Navbar from '../components/Navbar/Navbar';
import { BrowserRouter as Router, Switch, Route } from 'react-router-dom';
import SignIn from '../pages/auth/SignIn';
function AuthStack({ user, app }) {
return (
<>
<Router>
<Switch>
<Route path="/auth">
<Navbar />
<Route path="/auth/signin" app={app} exact component={SignIn} />
</Route>
</Switch>
</Router>
</>
);
}
export default AuthStack;
I usually create a Private route and hanlde it like this
import React, { useState, useEffect } from 'react';
import app from './firebase';
import AuthStack from './stacks/AuthStack';
import AppStack from './stacks/AppStack';
function AuthNavigator() {
const [initializing, setInitializating] = useState(true);
const [user, setUser] = useState(true);
const PrivateRoute = ({ component, ...rest }) => {
return (
<Route
{...rest}
render={(props) =>
user ? (
<Component {...props}></Component>
) : (
<Redirect to="/auth"></Redirect>
)
}
></Route>
);
};
function onAuthStateChanged(result) {
setUser(result);
if (initializing) setInitializating(false);
if (result) {
app
.auth()
.currentUser.getIdToken(/* force Refresh */ true)
.then((idToken) => {
result.idToken = idToken;
setUser(result);
})
.catch((error) => {
console.log(error.message);
});
}
}
useEffect(() => {
const authSubscriber = app.auth().onAuthStateChanged(onAuthStateChanged);
return authSubscriber;
});
if (initializing) {
return null;
}
return
<Router>
<Switch>
<PrivateRoute path="/app>
<Sidebar user={user} />
<PrivateRoute path="/app-melior/overview" exact component={Overview}></PrivateRoute>
</PrivateRoute>
<Route path="/auth">
<Navbar />
<Route path="/auth/signin" app={app} exact component={SignIn} />
</Route>
</Switch>
</Router>;
}
export default AuthNavigator;