I am trying to update a user when a token was changed. I have two hooks which are useToken (deals with token when login and logout events ) and useUser (update the user after any change in token value). I used them inside a HeaderHelper which is a functional component returns a Header Component that accept user, token, setToken and navigate as props. In the latter, I write a function called afterLogin to invoke setToken in the aim of updating the token field and invoke also a navigate hook to redirect to the appropriate route.
The problem is after logging in, the user field did not get any value and stay "null", Although the setToken and useEffect were invoked. To get the result that I aim to get I should to refresh the browser and it's obviously not practical.
quote NOTE: useUser has a token field as a part of its state. and the HeaderHelper has a user and token as a part of its state.
here is the code of useToken Hook:
export const useToken = () => {
const [ token, setToken ] = useState(()=>{
return sessionStorage.getItem('token');
});
function setTokenStorage (newToken) {
sessionStorage.setItem('token', newToken);
setToken(newToken);
}
return [token, setTokenStorage ];
}
here is the code of useUser Hook:
export const useUser = () => {
const [ token ] = useToken();
const getPayloadFromToken = token => {
console.log(token);
const encodedPayload = token.split('.')[1];
console.log(JSON.parse(atob(encodedPayload)), ': encodedPayload');
return JSON.parse(atob(encodedPayload));
}
const [ user, setUser] = useState(()=>{
// console.log('token from user useState', token , '***');
if(!token) {
console.log(token);
return {};
};
// console.log(getPayloadFromToken(token));
return getPayloadFromToken(token);
});
useEffect( ()=>{
console.log('token from user useEffect', token , '***');
if(!token) {
console.log('user will become null');
setUser({});
}else{
console.log(' useEffect ');
setUser(getPayloadFromToken(token));
}
},[token]);
return [user];
}
Here is the code of HeaderHelper Component:
export const HeaderHelper = (props) => {
const [user] = useUser();
const navigate = useNavigate();
const [token, setToken] = useToken();
return <Header navigate={ navigate } token={token} setToken={ setToken } user={ user } {...props}/>
}
Here is the code of Header Component:
class Header extends Component{
constructor (props){
super(props);
this.state = { ... }
}
handleLogin(e){
e.preventDefault();
this.setState({successSingIn : false});
fetch('http://localhost:8000/api/login', {
method: 'POST',
body: JSON.stringify({
"email" : this.email.value,
"password" : this.password.value,
"type" : this.type.value,
}),
headers: { "Content-Type" : "application/json"},
credentials: "same-origin"
}).then(response => {
if (response.ok){
return response;
}else{
const error = new Error('Error ' + response.status + ' : ' + response.statusText);
error.response = response;
throw error;
}
}).then(response => response.json())
.then(response => {
console.log('response from backend: ' + JSON.stringify(response));
if (response.status) {
this.setState({ failToLogIn : '' });
this.afterLogin(response.user.token);
}else{
this.setState({ failToLogIn : 'something wrong, try again with valid information!' });
}
})
.catch(error => {
this.setState({ failToLogIn : 'something wrong' });
console.log('something wrong with login process\n' , error.message)
});
}
}
and here is the code of afterLogin function:
afterLogin = (userToken) => {
this.toggleLoginModel();
console.log('userToken: ', userToken);
this.props.setToken(userToken);
this.props.navigate('/dashboard');
}
Summary: -This is how I think-
-1 the guest make a login request.
-2 afterLogin function invoked after a successfully login.
-3 then, setToken invoked, leads to invoke that function inside useEffect. And cause a re-render for HeaderHelper.
-4 finally, setUser will be invoked and cause a re-render for HeaderHelper.
-5 after all of that a token and user should have values, unfortunately that didn't happen.
So how can I solve this issue?
thank you!
Yeah, looks like each useToken hook maintains its own state. The setToken Header is using is from the useToken whereas nothing updates the token state of the useToken hook that useUsers is using, so the useEffect hook won't retrigger.
What you might try doing is to return the token and setToken from the useToken hook that useUser is using so there's only the one instance.
export const useUser = () => {
const [token, setToken] = useToken();
const getPayloadFromToken = token => {
console.log(token);
const encodedPayload = token.split('.')[1];
console.log(JSON.parse(atob(encodedPayload)), ': encodedPayload');
return JSON.parse(atob(encodedPayload));
}
const [user, setUser] = useState(() => {
// console.log('token from user useState', token , '***');
if (!token) {
console.log(token);
return {};
};
// console.log(getPayloadFromToken(token));
return getPayloadFromToken(token);
});
useEffect( ()=>{
console.log('token from user useEffect', token , '***');
if (!token) {
console.log('user will become null');
setUser({});
} else {
console.log(' useEffect ');
setUser(getPayloadFromToken(token));
}
}, [token]);
return [user, token, setToken];
}
...
export const HeaderHelper = (props) => {
const [user, token, setToken] = useUser();
const navigate = useNavigate();
return (
<Header
{...props}
navigate={navigate}
token={token}
setToken={setToken}
user={user}
/>
);
}
Related
I have used the context in other places, such as login, database functions, and more. However, when I try to run functions or variables inside my context in places such as custom api's or getServerSideProps, it returns the following error, TypeError: Cannot read properties of null (reading 'useContext'). I am attaching my auth context, my initialization of the context, and the getServerSideProps function that is returning an error
_app.js
import RootLayout from '../components/Layout'
import { AuthProvider } from '../configs/auth-context'
import '../styles/globals.css'
export default function App({ Component, pageProps }) {
return (
<AuthProvider >
<RootLayout>
<Component {...pageProps} />
</RootLayout>
</AuthProvider>
)}
auth-context
import React, { useContext, useState, useEffect, useRef } from 'react'
import { auth, db, provider } from './firebase-config'
import { GoogleAuthProvider, signInWithEmailAndPassword, createUserWithEmailAndPassword, signOut, onAuthStateChanged, signInWithPopup } from 'firebase/auth'
import { doc, getDoc, setDoc } from 'firebase/firestore'
import {useRouter} from 'next/router';
const AuthContext = React.createContext({currentUser: {uid: "TestUid", email:"Testeremail#email.com"}})
export function UseAuth() {
return useContext(AuthContext)
}
export function AuthProvider({ children }) {
const router = useRouter();
const [currentUser, setCurrentUser] = useState({uid: "TestUid", email:"Testeremail#email.com"})
const [loading, setLoading] = useState(true)
async function signup(email, password) {
createUserWithEmailAndPassword(auth, email, password)
.then(async (result) => {
const user = result.user;
await userToDb(user);
router.push('/portfolio');
return user;
}).catch((error) => {
console.error(error);
})
return
}
async function login(email, password) {
return signInWithEmailAndPassword(auth, email, password)
.then(async (result) => {
const user = result.user;
await userToDb(user);
router.push('/portfolio');
return user;
}).catch((error) => {
console.error(error)
})
}
function logout() {
router.push('/')
return signOut(auth)
}
async function googleSignIn() {
const provider = new GoogleAuthProvider();
signInWithPopup(auth, provider)
.then(async (result) => {
const credential = GoogleAuthProvider.credentialFromResult(result);
const token = credential.accessToken;
// The signed-in user info.
const user = result.user;
await userToDb(user);
router.push('/portfolio');
return user
}).catch((error) => {
console.log(error)
// const errorCode = error.code;
// const errorMessage = error.message;
// The email of the user's account used.
// const email = error.customData.email;
// The AuthCredential type that was used.
// const credential = GoogleAuthProvider.credentialFromError(error);
} )
}
const userToDb = async (user) => {
// await setDoc(doc(db, "users", user.uid), {
// userEmail: user.email,
// userID: user.uid
// }, {merge: false})
let currentRef = doc(db, 'users', user.uid)
let currentUserID = user.uid;
let currentEmail = user.email;
await setDoc(currentRef, {
userEmail: currentEmail,
userID: currentUserID
}, {merge: false})
}
function fixData(docs) {
console.log("this works")
// setDocuments(docs);
let retMap = new Map();
if (currentUser !== null) {
docs?.map(function(doc) {
console.log(doc)
let tic = doc.stockTicker
let data = {
shares: doc.shares,
price: doc.price,
type: doc.type
}
if(!retMap.has(tic)) {
retMap.set(tic, [data]);
console.log(tic + " " + data)
// setMap(new Map(datamap.set(tic, {shares: shares, averagePrice: price})))
}
else {
let x = retMap.get(tic);
x.push(data);
}
})
console.log(retMap)
return retMap;
}
}
useEffect(() => {
const unsubscribe = onAuthStateChanged(auth, async user => {
setCurrentUser(user)
setLoading(false)
})
return unsubscribe
}, [])
const value = {
currentUser,
login,
signup,
logout,
googleSignIn,
fixData
}
return (
<AuthContext.Provider value={value}>
{!loading && children}
</AuthContext.Provider>
)
}
getServerSideProps
export async function getServerSideProps() {
let allDocs = []
let avgDocs = []
const {currentUser} = UseAuth()
return {
props: {allDocs, avgDocs}
}
}
I don't know the correct answer, but hooks should be used in components and hooks without exception to ssr.
I want to make a Logout function when the token has expired. There is an AuthProvider in my application:
const AuthContext = createContext({});
export const AuthProvider = ({ children }) => {
const [auth, setAuth] = useState({ token: localStorage.getItem("access_token") });
return (
<AuthContext.Provider value={{ auth, setAuth }}>
{children}
</AuthContext.Provider>
)
}
export default AuthContext;
Now that the token has expired I need to call the setAuth hook and write an empty token there:
const logout = () =>{
const axiosInstance = axios.create({
withCredentials: true
})
axiosInstance.get("http://localhost:8080/api/auth/logout")
.then((response) => {
console.log(response)
})
.catch((error) => {
console.log(error.config);
});
window.location.href = '/auth'
};
const Logout = () => {
const {auth,setAuth} = useAuth();
const token = '';
setAuth({token});
localStorage.removeItem("access_token");
localStorage.clear();
logout()
};
export default Logout;
I am exporting this function in another file and want to call if the backend returns a response about an expired token.
const getStockData = async () => {
return instance.get(`/api/stock/symbols/${slug}`);
}
useEffect(() => {
(async () => {
const response = await getStockData();
console.log(response)
const data = response.data;
const stockInfo = data.chart.result[0];
console.log(stockInfo);
setPrice(stockInfo.meta.regularMarketPrice.toFixed(2));
setPriceTime(new Date(stockInfo.meta.regularMarketTime * 1000));
setSymbol(stockInfo.meta.symbol);
const quote = stockInfo.indicators.quote[0];
const prices = stockInfo.timestamp.map((timestamp, index) => ({
x: new Date(timestamp * 1000),
y: [quote.open[index], quote.high[index], quote.low[index], quote.close[index]].map(round)
}));
setPriceInfo([{
data: prices,
}]);
setStockData({ data });
})().catch(
(error) =>{
Logout()
}
);
}, []);
Here getStockData can return 403 if the token has expired.
But of course I get an error saying that the hook can't be used in a function like that. And I can't find a solution how to wrap or to do something similar so that this function can be called?
React doesn't let you initialize hooks inside of non-component functions. Instead, you can initialize the hook on the component level and let whatever function needs the hook's values to accept them as arguments.
const Logout = (auth, setAuth) => {
const token = '';
setAuth({token});
localStorage.removeItem("access_token");
localStorage.clear();
logout()
};
export default Logout;
// Initialize the hook at the component level
const {auth, setAuth} = useAuth();
.catch(
(error) =>{
// then pass the values from above to this function
Logout(auth, setAuth)
}
);
I have a signup/login workflow in React (NextJS), and everything is working correctly; i made a custom hook to remotely check if the user is authenticated based on localStorage jwt:
import React, { useState, useEffect } from 'react';
import axios from '../lib/api';
const useUser = () => {
const [logged, setIsLogged] = useState(false);
const [user, setUser] = useState('');
useEffect(async () => {
const jwt = localStorage.getItem('jwt');
if (!jwt) return;
await axios
.get('/api/users/me', {
headers: {
Authorization: `Bearer ${jwt}`,
},
})
.then((response) => {
setUser(response.data);
setIsLogged(true);
})
.catch((error) => {});
}, []);
return [logged, user, setIsLogged];
};
export default useUser;
This hooks works corectly in 99% of cases, but when i go on login form page, the login form flashes for a sec to logged in users, since the logged status is false before the check is initialized
import React, { useEffect, useState } from 'react';
import useUser from '../../lib/useUser';
import { useRouter } from 'next/router';
import LoginForm from '../../components/LoginForm';
function Login() {
const { push } = useRouter();
const [logged] = useUser();
console.log(ljwt, logged);
if (logged) {
//push('/');
return <p>nored</p>;
}
if (!logged) {
return <LoginForm />;
}
}
export default Login;
how can i avoid this? i tried to pass to useUser the jwt, so it assume the user is logged in while performing the remote check, but it is not really working as expected.
any suggestion?
Don't render the login form when the login state is still indeterminate.
By the way, useEffect functions can't be async in themselves, since they need to either return nothing or a cleanup function; async functions always return a promise.
async function getLoginState() {
const jwt = localStorage.getItem("jwt");
if (!jwt) return [false, null];
const resp = await axios.get("/api/users/me", {
headers: {
Authorization: `Bearer ${jwt}`,
},
});
return [true, response.data];
}
/**
* Get user login state.
*
* Returns undefined if the login state is not yet known.
* Returns a 2-item array [loginState, user] otherwise.
* `user` can be null when `loginState` is false.
*/
function useLoginState() {
const [loginState, setLoginState] = useState(undefined);
useEffect(() => {
getLoginState().then(setLoginState);
}, []);
return loginState;
}
function Login() {
const { push } = useRouter();
const loginState = useLoginState();
if (loginState === undefined) {
return <>Loading...</>;
}
const [logged, user] = loginState;
if (logged) {
return <p>Hi, {JSON.stringify(user)}</p>;
} else {
return <LoginForm />;
}
}
For context, I am working with integrating a Django Rest Framework backend with Next.js + Next-Auth. I have most of the integration down, except one part. The requirement is to have a refresh token system that will try to refresh the access token when it is almost expired. Here is the logic that I have:
/api/auth/[...nextauth].ts
import { NextApiRequest, NextApiResponse } from "next";
import NextAuth from "next-auth";
import { NextAuthOptions } from "next-auth";
import Providers from "next-auth/providers";
import axios from "axios";
import { AuthenticatedUser } from "../../../types";
import { JwtUtils, UrlUtils } from "../../../constants/Utils";
namespace NextAuthUtils {
export const refreshToken = async function (refreshToken) {
try {
const response = await axios.post(
// "http://localhost:8000/api/auth/token/refresh/",
UrlUtils.makeUrl(
process.env.BACKEND_API_BASE,
"auth",
"token",
"refresh",
),
{
refresh: refreshToken,
},
);
const { access, refresh } = response.data;
// still within this block, return true
return [access, refresh];
} catch {
return [null, null];
}
};
}
const settings: NextAuthOptions = {
secret: process.env.SESSION_SECRET,
session: {
jwt: true,
maxAge: 24 * 60 * 60, // 24 hours
},
jwt: {
secret: process.env.JWT_SECRET,
},
providers: [
Providers.Google({
clientId: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
}),
],
callbacks: {
async signIn(user: AuthenticatedUser, account, profile) {
// may have to switch it up a bit for other providers
if (account.provider === "google") {
// extract these two tokens
const { accessToken, idToken } = account;
// make a POST request to the DRF backend
try {
const response = await axios.post(
// tip: use a seperate .ts file or json file to store such URL endpoints
// "http://127.0.0.1:8000/api/social/login/google/",
UrlUtils.makeUrl(
process.env.BACKEND_API_BASE,
"social",
"login",
account.provider,
),
{
access_token: accessToken, // note the differences in key and value variable names
id_token: idToken,
},
);
// extract the returned token from the DRF backend and add it to the `user` object
const { access_token, refresh_token } = response.data;
user.accessToken = access_token;
user.refreshToken = refresh_token;
return true; // return true if everything went well
} catch (error) {
return false;
}
}
return false;
},
async jwt(token, user: AuthenticatedUser, account, profile, isNewUser) {
if (user) {
const { accessToken, refreshToken } = user;
// reform the `token` object from the access token we appended to the `user` object
token = {
...token,
accessToken,
refreshToken,
};
// remove the tokens from the user objects just so that we don't leak it somehow
delete user.accessToken;
delete user.refreshToken;
return token;
}
// token has been invalidated, try refreshing it
if (JwtUtils.isJwtExpired(token.accessToken as string)) {
const [
newAccessToken,
newRefreshToken,
] = await NextAuthUtils.refreshToken(token.refreshToken);
if (newAccessToken && newRefreshToken) {
token = {
...token,
accessToken: newAccessToken,
refreshToken: newRefreshToken,
iat: Math.floor(Date.now() / 1000),
exp: Math.floor(Date.now() / 1000 + 2 * 60 * 60),
};
return token;
}
// unable to refresh tokens from DRF backend, invalidate the token
return {
...token,
exp: 0,
};
}
// token valid
return token;
},
async session(session, userOrToken) {
session.accessToken = userOrToken.accessToken;
return session;
},
},
};
export default (req: NextApiRequest, res: NextApiResponse) =>
NextAuth(req, res, settings);
Next, the example in the Next-Auth documentation shows the use of useSession() hook. But I am not a fan of it because:
It does not update the state of the session once the access token is refreshed unless the window itself is refreshed (it is an open issue)
It feels like a lot of code repetition on every component that wants to use the session, with the guards that check the existence of session object, whether the session is loading etc. So I wanted to use a HOC.
As such, I came up with the following solutions:
constants/Hooks.tsx
import { Session } from "next-auth";
import { useEffect, useMemo, useState } from "react";
export function useAuth(refreshInterval?: number): [Session, boolean] {
/*
custom hook that keeps the session up-to-date by refreshing it
#param {number} refreshInterval: The refresh/polling interval in seconds. default is 10.
#return {tuple} A tuple of the Session and boolean
*/
const [session, setSession] = useState<Session>(null);
const [loading, setLoading] = useState<boolean>(false);
useEffect(() => {
async function fetchSession() {
let sessionData: Session = null;
setLoading(true);
const response = await fetch("/api/auth/session");
if (response.ok) {
const data: Session = await response.json();
if (Object.keys(data).length > 0) {
sessionData = data;
}
}
setSession(sessionData);
setLoading(false);
}
refreshInterval = refreshInterval || 10;
fetchSession();
const interval = setInterval(() => fetchSession(), refreshInterval * 1000);
return () => clearInterval(interval);
}, []);
return [session, loading];
}
constants/HOCs.tsx
import { Session } from "next-auth";
import { signIn } from "next-auth/client";
import React from "react";
import { useAuth } from "./Hooks";
type TSessionProps = {
session: Session;
};
export function withAuth<P extends object>(Component: React.ComponentType<P>) {
return React.memo(function (props: Exclude<P, TSessionProps>) {
const [session, loading] = useAuth(); // custom hook call
if (loading) {
return <h2>Loading...</h2>;
}
if (!loading && !session) {
return (
<>
Not signed in <br />
<button onClick={() => signIn()}>Sign in</button>
<pre>{!session && "User is not logged in"}</pre>
</>
);
}
return <Component {...props} session={session} />;
});
}
Then, in a component where I have periodic data fetching requirements (I know this could be achieved in a much better way, this is just a contrived example where I am trying to simulate user inactivity but the app can still work in the background if needed), I am using the HOC:
pages/posts.tsx
import React, { useEffect, useState } from "react";
import Post from "../components/Post";
import { withAuth } from "../constants/HOCs";
import { TPost } from "../constants/Types";
import Link from "next/link";
function Posts(props) {
const { session } = props;
// const [session, loading] = useAuth();
const [posts, setPosts] = useState<TPost[]>([]);
const [fetchingPosts, setFetchingPosts] = useState<boolean>(false);
useEffect(() => {
if (!session) {
return;
}
async function getPosts() {
setFetchingPosts(true);
const response = await fetch("http://127.0.0.1:8000/api/posts", {
method: "get",
headers: new Headers({
Authorization: `Bearer ${session?.accessToken}`,
}),
});
if (response.ok) {
const posts: TPost[] = await response.json();
setPosts(posts);
}
setFetchingPosts(false);
}
// initiate the post fetching mechanism once
getPosts();
const intervalId = setInterval(() => getPosts(), 10 * 1000);
// useEffect cleanup
return () => clearInterval(intervalId);
}, [JSON.stringify(session)]);
// {
// loading && <h2>Loading...</h2>;
// }
// {
// !loading && !session && (
// <>
// Not signed in <br />
// <button onClick={() => signIn()}>Sign in</button>
// <pre>{!session && "User is not logged in"}</pre>
// </>
// );
// }
return (
<div>
<h2>Fetched at {JSON.stringify(new Date())}</h2>
<Link href="/">Back to homepage</Link>
{posts.map((post) => (
<Post key={post.title} post={post} />
))}
</div>
);
}
export default withAuth(Posts);
The problem is that the entire page gets re-rendered due to the withAuth HOC and possibly due to the useAuth hook every 10 seconds. However, I have had no luck trying to debug it. Maybe I am missing something key in my React concepts. I appreciate any and all suggestions/help possible. Thanks in advance.
PS. I am aware of a solution that uses SWR library, but I would like to avoid using that library if at all possible.
I ended up using the useSwr() hook after spending an unworldly amount of time trying to fix this issue. Also ended up writing this article for those who are interested.
I've been chasing my tail for hours now trying to figure out how to handle auth on my component using firebase and react hooks.
I've created a custom useAuth hook that is intended to handle all the auth behaviors. My thought was to put a useEffect on the root of my component tree that would trigger if the firebase.auth.onAuthStateChanged() ever changed (ie, user is now logged out / logged in.) But, at this point after making a million unsuccessful changes I really don't know what I'm doing anymore.
Here is the code that I have...
RootPage component
const RootPage = ({ Component, pageProps }): JSX.Element => {
const { logoutUser, authStatus } = useAuth();
const router = useRouter();
useEffect(() => {
authStatus();
}, [authStatus]);
...
}
my thought was ok, lets trigger authStatus on mount, but that ends up with me lying about my dependencies. So, in an effort to not lie about my deps, I added authStatus to the deps. Logging out and then logging in results in this:
useAuth hook
const useAuth = () => {
const { fetchUser, resetUser, userData } = useUser();
const { currentUser } = firebaseAuth;
const registerUser = async (username, email, password) => {
try {
const credentials = await firebaseAuth.createUserWithEmailAndPassword(
email,
password
);
const { uid } = credentials.user;
await firebaseFirestore
.collection('users')
.doc(credentials.user.uid)
.set({
username,
points: 0,
words: 0,
followers: 0,
following: 0,
created: firebase.firestore.FieldValue.serverTimestamp(),
});
fetchUser(uid);
console.log('user registered', credentials);
} catch (error) {
console.error(error);
}
};
const loginUser = async (email, password) => {
try {
// login to firebase
await firebaseAuth.signInWithEmailAndPassword(email, password);
// take the current users id
const { uid } = firebaseAuth.currentUser;
// update the user in redux
fetchUser(uid);
} catch (error) {
console.error(error);
}
};
const logoutUser = async () => {
try {
// logout from firebase
await firebaseAuth.signOut();
// reset user state in redux
resetUser();
return;
} catch (error) {
console.error(error);
}
};
const authStatus = () => {
firebaseAuth.onAuthStateChanged((user) => {
if (user) {
console.log('User logged in.');
// On page refresh, if user persists (but redux state is lost), update user in redux
if (userData === initialUserState) {
console.log('triggered');
// update user in redux store with data from user collection
fetchUser(user.uid);
}
return;
}
console.log('User logged out.');
});
};
return { currentUser, registerUser, loginUser, logoutUser, authStatus };
};
export default useAuth;
I'm relatively certain that react hooks are only meant for reusable pieces of logic, so if the purpose of your hook is to contact firebase in every single component you're using it, along with rerendering and refreshing state every time that component is updated, then it's fine, but you can't use hooks for storing global auth state, which is how auth should be stored.
You're looking for react context instead.
import React, {createContext, useContext, useState, useEffect, ReactNode} from 'react'
const getJwt = () => localStorage.getItem('jwt') || ''
const setJwt = (jwt: string) => localStorage.setItem('jwt', jwt)
const getUser = () => JSON.parse(localStorage.getItem('user') || 'null')
const setUser = (user: object) => localStorage.setItem('user', JSON.stringify(user))
const logout = () => localStorage.clear()
const AuthContext = createContext({
jwt: '',
setJwt: setJwt,
user: {},
setUser: setUser,
loading: false,
setLoading: (loading: boolean) => {},
authenticate: (jwt: string, user: object) => {},
logout: () => {},
})
export const useAuth = () => useContext(AuthContext)
const Auth = ({children}: {children: ReactNode}) => {
const auth = useAuth()
const [jwt, updateJwt] = useState(auth.jwt)
const [user, updateUser] = useState(auth.user)
const [loading, setLoading] = useState(false)
useEffect(() => {
updateJwt(getJwt())
updateUser(getUser())
}, [])
const value = {
jwt: jwt,
setJwt: (jwt: string) => {
setJwt(jwt)
updateJwt(jwt)
},
user: user,
setUser: (user: object) => {
setUser(user)
updateUser(user)
},
loading: loading,
setLoading: setLoading,
authenticate: (jwt: string, user: object) => {
setJwt(jwt)
updateJwt(jwt)
setUser(user)
updateUser(user)
},
logout: () => {
localStorage.removeItem('jwt')
localStorage.removeItem('user')
updateJwt('')
updateUser({})
setLoading(false)
},
}
return <AuthContext.Provider value={value}>
{children}
</AuthContext.Provider>
}
export default Auth
...
// app.tsx
import Auth from './auth'
...
<Auth>
<Router/>
</Auth>
// or something like that
...
import {useAuth} from './auth'
// in any component to pull auth from global context state
You can change that according to whatever you need.
I know the issue why its happening but don't know the solution...But i am not fully sure...Look how react works is if any parents re render it also cause re render the children..ok?Its mean if any reason your apps is re rendering and the useAuth keep firing...so for this there to much console log.But i am not sure that it will work or not..give me your repo i will try on my local computer
const RootPage = ({ Component, pageProps }): JSX.Element => {
const { logoutUser, authStatus,currentUser } = useAuth();
const router = useRouter();
useEffect(() => {
authStatus();
}, [currentUser]);
//only fire when currentUser change
...
}
Update your useEffect hook like so:
useEffect(() => {
const unsub = firebaseAuth.onAuthStateChanged((user) => {
if (user) {
console.log('User logged in.');
// On page refresh, if user persists (but redux state is lost), update user in redux
if (userData === initialUserState) {
console.log('triggered');
// update user in redux store with data from user collection
fetchUser(user.uid);
}
} else {
console.log('User logged out.');
}
});
return ()=> unsub;
},[])