nextjs firebaseauth redirect before render page if logged in - javascript

I have a login page and an auth provider as follow
import * as yup from "yup";
import { useState } from 'react'
import { AuthErrorCodes } from 'firebase/auth'
import Cover from '../../components/Cover'
import Head from 'next/head'
import Headingblock from '../../components/Headingblock'
import Section from '../../components/Section'
import { authErrorCodesTranslation } from '../../config/firebase';
import { siteTitle } from '../_app'
import { useForm } from 'react-hook-form';
import { yupResolver } from '#hookform/resolvers/yup';
import { useRouter } from 'next/router';
import { useAuth } from "../../contexts/auth";
import { auth } from '../../config/firebase';
import { signInWithEmailAndPassword } from "firebase/auth";
import {useEffect} from 'react'
export default function Login() {
const [response, setResponse] = useState({});
const [user, userLoading] = useAuth();
const router = useRouter();
console.log(userLoading);
if (!userLoading && user)
router.push('/');
const schema = yup.object().shape({
email: yup.string().email('Adresse email invalide').required('Veuillez renseigner votre adresse email.'),
password: yup.string().required('Veuillez renseigner votre mot de passe')
});
const { register, handleSubmit, formState } = useForm({
resolver: yupResolver(schema),
});
const { errors } = formState;
const onSubmit = async (data) => {
await signInWithEmailAndPassword(auth, data.email, data.password)
.then(userCredential => {
router.push("/profile");
}).catch(error => {
const authErrorCode = Object.keys(AuthErrorCodes).find(k => AuthErrorCodes[k] === error.message);
let errorTranslation = authErrorCodesTranslation.GLOBAL_AUTH_ERROR;
if (authErrorCode !== undefined)
errorTranslation = authErrorCodesTranslation[authErrorCode];
setResponse({ status: 'error', message: errorTranslation });
})
}
return (
<>
<Head>
<title>{siteTitle} - Connexion</title>
</Head>
<Cover />
<Section>
<div className='container marginNTop100'>
<div style={{ padding: '3rem 1.5rem', background: 'white', boxShadow: '0 30px 50px 0 rgb(1 1 1 / 15%)' }}>
<Headingblock main="Connexion" />
<form onSubmit={handleSubmit(onSubmit)}>
<div className="form-row">
<div className="form-group col" style={formgroupStyle}>
<label style={labelStyle}>Adresse email</label>
<input {...register("email")} type="text" className='form-control' style={formcontrolStyle} />
<div className="invalid-feedback" style={invalidStyle}>{errors.email?.message}</div>
</div>
<div className="form-group col" style={formgroupStyle}>
<label style={labelStyle}>Mot de passe</label>
<input {...register("password")} type="password" className='form-control' style={formcontrolStyle} />
<div className="invalid-feedback" style={invalidStyle}>{errors.password?.message}</div>
</div>
</div>
<div className="form-group" style={formgroupStyle}>
<button type="submit" style={Object.assign(btnStyle, btnprimaryStyle)}>
Connexion
</button>
</div>
{
response.status === 'error' && <div style={Object.assign(alertStyle, alertDangerStyle)}>{response.message}</div>
}
</form>
</div>
</div>
</Section>
</>
)
}
import { createContext, useState, useEffect, useContext } from 'react';
import { onAuthStateChanged } from 'firebase/auth';
import { auth } from '../config/firebase'
const AuthContext = createContext({ user: null, userLoading: true });
export const AuthProvider = ({ children }) => {
const [userLoading, setUserLoading] = useState(true);
const [user, setUser] = useState();
useEffect(() => {
return onAuthStateChanged(auth, (user) => {
console.log('autstatechanged');
if (user) {
console.log("1");
setUser(user);
setUserLoading(false);
// ...
} else {
console.log("2");
setUser(null);
}
});
}, []);
return (
<AuthContext.Provider value={[user, userLoading]}>
{children}
</AuthContext.Provider>
);
};
export const useAuth = () => useContext(AuthContext);
Once user is logged in I would like that page being not available anymore so I made a redirect once user is set and userloading is false but the page is rendering before redirect that causes a blink effect.
I wouldn't like return a loading screen I think It should be possible to not render login page if user is logged in

Related

useNavigate redirecting but not loading

the URL shows that it is login page but everything is white, and when I reload it loads the page.
in the console says that many variables from the previous page are null, it seems like it's still on the previous page. why is this happening?
The previous page and the code:
import React, { useState } from 'react'
import { useNavigate } from 'react-router-dom'
import '../styles/UserProfile.css'
import { useAuth } from '../services/authContext'
export default function UserProfile() {
const { currentUser, logout } = useAuth()
const navigate = useNavigate()
const [errorMessage, setErrorMessage] = useState(null)
async function handleLogout() {
try {
await logout()
navigate("/login")
} catch (error) {
console.log(error)
setErrorMessage('An error ocurred when trying to logout')
}
}
return (
<div className="container profile">
<div className="profile">
<h1>Profile</h1>
{errorMessage && (
<div className="error-container">
<strong>{errorMessage}</strong>
</div>
)}
<div className="row">
<h3>Email</h3>
<p>{currentUser.email}</p>
</div>
<div className="row">
<a>Change email</a>
</div>
<div className="row">
<a>Reset password</a>
</div>
<button onClick={handleLogout}>Logout</button>
</div>
</div>
)
}
and when i click logout this happens:
when i reload it loads normally
useAuth code:
import React, { createContext, useContext, useEffect, useState } from 'react'
import {
createUserWithEmailAndPassword,
onAuthStateChanged,
signInWithEmailAndPassword,
signOut,
} from 'firebase/auth'
import { auth } from '../firebase'
const AuthContext = createContext()
export function useAuth() {
return useContext(AuthContext)
}
export function AuthProvider({ children }) {
const [currentUser, setCurrentUser] = useState()
function signUp(email, password) {
return createUserWithEmailAndPassword(auth, email, password)
}
function login(email, password) {
return signInWithEmailAndPassword(auth, email, password)
}
function logout() {
return signOut(auth)
}
useEffect(() => {
const unsubscribe = onAuthStateChanged(auth, user => {
setCurrentUser(user)
})
return unsubscribe
}, [])
return (
<AuthContext.Provider
value={{
signUp: signUp,
login: login,
logout: logout,
currentUser: currentUser,
}}
>
{children}
</AuthContext.Provider>
)
}
login code:
import React, { useState } from 'react'
import { useAuth } from '../services/authContext'
import { useNavigate, Link } from 'react-router-dom'
import Header from '../components/Header'
export default function Login() {
const { login } = useAuth()
const navigate = useNavigate()
const [email, setEmail] = useState('')
const [password, setPassword] = useState('')
const [loading, setLoading] = useState(false)
const [errorMessage, setErrorMessage] = useState(null)
async function handleSubmit(element) {
element.preventDefault()
setLoading(true)
if (password.length < 6) {
setErrorMessage('The password need to have at least 6 characters!')
setLoading(false)
return
}
try {
await login(email, password)
navigate('/')
} catch (error) {
setErrorMessage('An error occured when trying to login')
}
setLoading(false)
}
return (
<div className="container">
<Header />
<h2>Login</h2>
{errorMessage && (
<div className="error-container">
<strong>{errorMessage}</strong>
</div>
)}
<form onSubmit={handleSubmit}>
<label>Email</label>
<input
type="email"
value={email}
onChange={e => setEmail(e.target.value)}
/>
<label>Password</label>
<input
type="password"
value={password}
onChange={e => {
setPassword(e.target.value)
}}
/>
<button disabled={loading} className="button-block" type="submit1">
Login
</button>
</form>
<div className="center">
<p>
ForgotPassword ? <Link to="/forgot-password">Reset password</Link>
</p>
<p>
Don't have an account ? <Link to="/signup">Create account</Link>
</p>
</div>
</div>
)
}
i consoled log currentUser and currentUser.email before logout, and it's not null, this happens after logging out.
UserProfile.jsx:32:1 is <p>{currentUser.email}</p>. As far as I can tell it appears the currentUser object is nullified and the UserProfile component is rerendered at least once prior to the navigation action being effected to navigate the user to the "/login" path. The UserProfile component falls over when attempting to access currentUser.email when currentUser is null.
You should place a guard on the UI to only render valid content when currentUser is non-null.
Example:
export default function UserProfile() {
const { currentUser, logout } = useAuth();
const navigate = useNavigate();
const [errorMessage, setErrorMessage] = useState(null);
async function handleLogout() {
try {
await logout();
navigate("/login");
} catch (error) {
console.log(error);
setErrorMessage('An error ocurred when trying to logout');
}
}
if (!currentUser) {
return <div>No Current user. Log in.</div>;
}
return (
<div className="container profile">
<div className="profile">
<h1>Profile</h1>
{errorMessage && (
<div className="error-container">
<strong>{errorMessage}</strong>
</div>
)}
<div className="row">
<h3>Email</h3>
<p>{currentUser.email}</p>
</div>
<div className="row">
<a>Change email</a>
</div>
<div className="row">
<a>Reset password</a>
</div>
<button onClick={handleLogout}>Logout</button>
</div>
</div>
)
}

setLoggedIn is not a function react

Okay I'm trying to use global states to store if an user is logged in or not. To do so, I've created a Context file as follows:
import { createContext } from "react";
export const LoginContext= createContext({})
I also have my App.jsx:
import React, { useState } from 'react';
import Component1 from './Component1.jsx';
import Component2 from './Component2.jsx';
import Component3 from './Component3.jsx';
import { LoginContext } from '../Helper/Context.js';
function App(){
const [loggedIn, setLoggedIn] = useState(false);
return (
<LoginContext.Provider value={{loggedIn, setLoggedIn}}>
<Component1 />
<Component2 />
<Component3 />
</LoginContext.Provider>
)
}
export default App;
And then I have my Login component:
import React, {useState, useContext} from "react";
import Axios from 'axios';
import { Link, useHistory } from 'react-router-dom';
import { LoginContext } from "../Helper/Context";
import NavbarHome from "./NavbarHome";
function Login()
{
Axios.defaults.withCredentials = true;
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [loginStatus, setLoginStatus] = useState(false);
const {loggedIn, setLoggedIn} = useContext(LoginContext);
const [error, setErrorStatus] = useState("");
let history = useHistory();
let user = {
id: null,
nombre: null,
email: null
};
const login = () => {
Axios.post('http://localhost:3001/login', {
email: email,
password: password,
}).then((response)=>{
if(!response.data.auth) {
setLoginStatus(false);
setErrorStatus(response.data.message)
setLoggedIn(false);
}
else{
localStorage.setItem("token", response.data.token)
user = {
id: response.data.result[0].id,
name: response.data.result[0].name,
email: response.data.result[0].email
}
setLoginStatus(true);
setLoggedIn(true);
history.push('/perfil');
}
});
};
const userAuthenticated = () => {
Axios.get("http://localhost:3001/isUserAuth", {
headers: {
"x-access-token": localStorage.getItem("token"),
},
}).then((response) => {
console.log(response);
});
}
return(
<div>
<div>
<NavbarHome />
<div>
<div>
<h1>Login</h1>
<p className="label-login">Email:</p>
<input type="text" placeholder="Email..." onChange={(event) => {setEmail(event.target.value);}}/>
<p className="label-login">Contraseña:</p>
<input type="password" placeholder="Password..." onChange={(event) => {setPassword(event.target.value);}}/> <br />
<button onClick={login}>Login</button>
<p style={{marginTop: '1.3rem', color: 'red'}}>{error}</p>
<p><Link to='/registro'>Register here!</Link></p>
</div>
</div>
</div>
</div>
);
}
export default Login;
The output that I receive is this:
It complains about the line where I do this: setLoggedIn(false); or setLoggedIn(true); I'd like to use that state instead of setLoginStatus (which is the one I'm currently using)
Any ideas on how to fix it?

Problem with JS functions, using Firebase and React

thanks for your patience. I'm implementing Firebase on my site but when I call the signup function in FormUp.js (declared in AuthContext.js) it doesn't refer to the function definition. This causes the function called in FormUp.js, not to call its own function defined in AuthContext.js, but falls into the catch branch ('Failed to create an account'). I don't understand why. Hope someone can help me, thanks!
Error:
TypeError: _firebase__WEBPACK_IMPORTED_MODULE_1__.auth.createUserWithEmailAndPassword is not a function
at signup (bundle.js:4226:56)
at handleSubmit (bundle.js:2575:13)
at HTMLUnknownElement.callCallback (bundle.js:41062:18)
at Object.invokeGuardedCallbackDev (bundle.js:41111:20)
at invokeGuardedCallback (bundle.js:41171:35)
at invokeGuardedCallbackAndCatchFirstError (bundle.js:41186:29)
at executeDispatch (bundle.js:45421:7)
at processDispatchQueueItemsInOrder (bundle.js:45453:11)
at processDispatchQueue (bundle.js:45466:9)
at dispatchEventsForPlugins (bundle.js:45477:7)
Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function.
at Navbar (http://localhost:3000/static/js/bundle.js:3361:76)
at Home
Code:
AuthContext.js
import React, { useContext, useState, useEffect } from "react";
import { auth } from "../firebase";
import { createUserWithEmailAndPassword } from "firebase/auth";
const AuthContext = React.createContext();
export function useAuth() {
return useContext(AuthContext);
}
export function AuthProvider({ children }) {
const [currentUser, setCurrentUser] = useState();
const [loading, setLoading] = useState(true);
function signup(email, password) {
return auth.createUserWithEmailAndPassword(email, password);
}
function login(email, password) {
return auth.signInWithEmailAndPassword(email, password);
}
function logout() {
return auth.signOut();
}
function resetPassword(email) {
return auth.sendPasswordResetEmail(email);
}
function updateEmail(email) {
return currentUser.updateEmail(email);
}
function updatePassword(password) {
return currentUser.updatePassword(password);
}
useEffect(() => {
const unsubscribe = auth.onAuthStateChanged((user) => {
setCurrentUser(user);
setLoading(false);
});
return unsubscribe;
}, []);
const value = {
currentUser,
login,
signup,
logout,
resetPassword,
updateEmail,
updatePassword,
};
return (
<AuthContext.Provider value={value}>
{!loading && children}
</AuthContext.Provider>
);
}
firebase.js
import { initializeApp } from "firebase/app";
import { getAuth } from "firebase/auth";
const firebaseConfig = {
//Hidden
};
const app = initializeApp(firebaseConfig);
const auth = getAuth();
export { app, auth };
FormUp.js
import React, { useRef, useState } from "react";
import { Link } from "react-router-dom";
import {
FormUser,
Input,
Label,
Subtitle,
TextWrapper,
TopLine,
FormButton,
Credentials,
HomePage,
SignInLink,
SignInText,
RedirectSignIn,
Credential,
} from "./Form.elements";
import { FaAngleLeft } from "react-icons/fa";
import { useAuth } from "../../contexts/AuthContext";
import { Alert } from "bootstrap";
const FormUp = ({
primary,
lightBg,
lightTopLine,
lightTextDesc,
buttonLabel,
description,
topLine,
}) => {
const emailRef = useRef();
const passwordRef = useRef();
const passwordConfirmRef = useRef();
const { signup } = useAuth();
const [error, setError] = useState("");
const [loading, setLoading] = useState(false);
async function handleSubmit(e) {
e.preventDefault();
if (passwordRef.current.value !== passwordConfirmRef.current.value) {
return setError("Password do not match");
}
try {
setError("");
setLoading(true);
await signup(emailRef.current.value, passwordRef.current.value);
} catch {
setError("Failed to create an account");
}
setLoading(false);
}
return (
<>
<style>
#import
url('https://fonts.googleapis.com/css2?family=Poppins:wght#100;200;300;400;500;600;700;800;900&display=swap');
</style>
<FormUser lightBg={lightBg} onSubmit={handleSubmit}>
<TextWrapper>
<HomePage href="/">
<FaAngleLeft />
<TopLine lightTopLine={lightTopLine}>{topLine}</TopLine>
</HomePage>
<Subtitle lightTextDesc={lightTextDesc}>{description}</Subtitle>
{error && (
<h5 style={{ color: "red", paddingBottom: "30px" }}>{error}</h5>
)}
<Credentials>
<Credential id="email">
<Label>Email</Label>
<Input
ref={emailRef}
required
type="email"
placeholder="Email..."
></Input>
</Credential>
<Credential id="password">
<Label>Password</Label>
<Input
ref={passwordRef}
required
type="password"
placeholder="Password..."
></Input>
</Credential>
<Credential id="password-confirm">
<Label>Password confirmation</Label>
<Input
ref={passwordConfirmRef}
required
type="password"
placeholder="Password confirmation..."
></Input>
</Credential>
</Credentials>
<FormButton disabled={loading} type="submit" big primary={primary}>
{buttonLabel}
</FormButton>
<RedirectSignIn>
<SignInText>Already have an account?</SignInText>
<SignInLink href="/sign-in">Log in</SignInLink>
</RedirectSignIn>
</TextWrapper>
</FormUser>
</>
);
};
export default FormUp;

Removing the token from local Storage

So here I have Login functionality via local storage token. I am getting the token upon user being created in The dev tools/Application. It is redirecting me to the home as it should. The thing that is not working is this. When I try to press the logout I am getting ×TypeError: Cannot read property 'push' of undefined it is showing the error in the Logout Handler function
Context Api
import React from "react";
import { useHistory } from "react-router-dom"
const UserContext = React.createContext();
function getUserFromLocalStorage() {
return localStorage.getItem("authToken")
? JSON.parse(localStorage.getItem("authToken"))
: { username: null, token: null };
}
function UserProvider({ children }) {
const [user, setUser] = React.useState(getUserFromLocalStorage());
const history = useHistory()
const logoutHandler = () =>{
localStorage.removeItem("authToken");
setUser(user);
history.push("/")
}
return (
<UserContext.Provider
value={{ user, setUser, logoutHandler }}
>
{children}
</UserContext.Provider>
);
}
export { UserContext, UserProvider };
Login Link
import React from "react";
import { Link } from "react-router-dom";
import { UserContext } from "../../context/user";
import { useHistory } from "react-router-dom"
export default function LoginLink() {
const { user, logoutHandler } = React.useContext(UserContext);
if (user.authToken) {
return (
<button
onClick={() => {
logoutHandler();
}}
className="login-btn"
>
logout
</button>
);
}
return <Link to="/login">login</Link>;
Header
import React from 'react';
import { Link, useHistory } from "react-router-dom";
import Search from './Search';
import './Header.css'
import SearchBooks from './SearchBooks';
import LoginLink from '../Signin/LoginLink';
import CartLink from '../Cart/CartLink';
import { UserContext } from '../../context/user';
const Header = () => {
const { user } = React.useContext(UserContext);
return (
<div className='header__container'>
<Link to='/'>
<img src='/audi.png' />
</Link>
<li>
<LoginLink />
</li>
{user.authToken &&(
<CartLink />
)
}
</div>
)
}
export default Header
Login
import { useState, } from "react";
import { useHistory } from "react-router-dom"
import axios from "axios";
import { Link } from "react-router-dom";
import "./Signin.css";
const Login = () => {
const { user, setUser } = React.useContext(UserContext);
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [error, setError] = useState("");
const history = useHistory()
const loginHandler = async (e) => {
e.preventDefault();
const config = {
header: {
"Content-Type": "application/json",
},
};
try {
const { data } = await axios.post(
"http://localhost:5000/api/auth/login",
{ email, password },
config
);
localStorage.setItem("authToken", data.token);
setUser(user)
history.push("/");
} catch (error) {
if (error.response) {
setError(error.response.data.error);
}
setTimeout(() => {
setError("");
}, 5000);
}
};
return (
<div className="login-screen">
<form onSubmit={loginHandler} className="login-screen__form">
<h3 className="login-screen__title">Login</h3>
{error && <span className="error-message">{error}</span>}
<div className="form-group">
<label htmlFor="email">Email:</label>
<input
type="email"
required
id="email"
placeholder="Email address"
onChange={(e) => setEmail(e.target.value)}
value={email}
tabIndex={1}
/>
</div>
<div className="form-group">
<label htmlFor="password">
Password:{" "}
<Link to="/forgotpassword" className="login-screen__forgotpassword">
Forgot Password?
</Link>
</label>
<input
type="password"
required
id="password"
autoComplete="true"
placeholder="Enter password"
onChange={(e) => setPassword(e.target.value)}
value={password}
tabIndex={2}
/>
</div>
<button type="submit" className="btn btn-primary">
Login
</button>
<span className="login-screen__subtext">
Don't have an account? <Link to="/register">Register</Link>
</span>
</form>
</div>
);
};
export default Login;

Logout funcionality via local storage

So here I have Login funcionality via local storage token. I am getting the token upon user being created in The dev tools/Application it is redirecting me to the home that is working. The thing that is not working is this. Creating an user it is working but when it loads it should show logout instead of login, and should hide the cart Component. Where am I making a mistake
Context Api
import React from "react";
const UserContext = React.createContext();
function getUserFromLocalStorage() {
return localStorage.getItem("authToken")
? JSON.parse(localStorage.getItem("authToken"))
: { username: null, token: null };
}
function UserProvider({ children }) {
const [user, setUser] = React.useState(getUserFromLocalStorage());
const history = useHistory();
const logoutHandler = () =>{
localStorage.removeItem("user");
history.push("/")
}
return (
<UserContext.Provider
value={{ user, logoutHandler }}
>
{children}
</UserContext.Provider>
);
}
export { UserContext, UserProvider };
Login Link
import React from "react";
import { Link } from "react-router-dom";
import { UserContext } from "../../context/user";
import { useHistory } from "react-router-dom"
export default function LoginLink() {
const { user, logoutHandler } = React.useContext(UserContext);
if (user.authToken) {
return (
<button
onClick={() => {
logoutHandler();
}}
className="login-btn"
>
logout
</button>
);
}
return <Link to="/login">login</Link>;
}
Header
import React from 'react';
import { Link, useHistory } from "react-router-dom";
import Search from './Search';
import './Header.css'
import SearchBooks from './SearchBooks';
import LoginLink from '../Signin/LoginLink';
import CartLink from '../Cart/CartLink';
import { UserContext } from '../../context/user';
const Header = () => {
const { user } = React.useContext(UserContext);
return (
<div className='header__container'>
<Link to='/'>
<img src='/audi.png' />
</Link>
<li>
<LoginLink />
</li>
{user.authToken &&(
<CartLink />
)
}
</div>
)
}
export default Header
Login Component
import { useState, } from "react";
import { useHistory } from "react-router-dom"
import axios from "axios";
import { Link } from "react-router-dom";
import "./Signin.css";
const Login = () => {
const { user, setUser } = React.useContext(UserContext);
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [error, setError] = useState("");
const history = useHistory()
const loginHandler = async (e) => {
e.preventDefault();
const config = {
header: {
"Content-Type": "application/json",
},
};
try {
const { data } = await axios.post(
"http://localhost:5000/api/auth/login",
{ email, password },
config
);
localStorage.setItem("authToken", data.token);
setUser(user)
history.push("/");
} catch (error) {
if (error.response) {
setError(error.response.data.error);
}
setTimeout(() => {
setError("");
}, 5000);
}
};
return (
<div className="login-screen">
<form onSubmit={loginHandler} className="login-screen__form">
<h3 className="login-screen__title">Login</h3>
{error && <span className="error-message">{error}</span>}
<div className="form-group">
<label htmlFor="email">Email:</label>
<input
type="email"
required
id="email"
placeholder="Email address"
onChange={(e) => setEmail(e.target.value)}
value={email}
tabIndex={1}
/>
</div>
<div className="form-group">
<label htmlFor="password">
Password:{" "}
<Link to="/forgotpassword" className="login-screen__forgotpassword">
Forgot Password?
</Link>
</label>
<input
type="password"
required
id="password"
autoComplete="true"
placeholder="Enter password"
onChange={(e) => setPassword(e.target.value)}
value={password}
tabIndex={2}
/>
</div>
<button type="submit" className="btn btn-primary">
Login
</button>
<span className="login-screen__subtext">
Don't have an account? <Link to="/register">Register</Link>
</span>
</form>
</div>
);
};
export default Login;
After you login, you have only set the authToken into your localStorage and not in your Provider. You'll need to call setUser after you login in order for Header component to know user is logged in.

Categories