How can I stay the user in the same page? - reactjs

Every time I reload the my account page, it will go to the log in page for a while and will directed to the Logged in Homepage. How can I stay on the same even after refreshing the page?
I'm just practicing reactjs and I think this is the code that's causing this redirecting to log-in then to home
//if the currentUser is signed in in the application
export const getCurrentUser = () => {
return new Promise((resolve, reject) => {
const unsubscribe = auth.onAuthStateChanged(userAuth => {
unsubscribe();
resolve(userAuth); //this tell us if the user is signed in with the application or not
}, reject);
})
};
.....
import {useEffect} from 'react';
import { useSelector } from 'react-redux';
const mapState = ({ user }) => ({
currentUser: user.currentUser
});
//custom hook
const useAuth = props => {
//get that value, if the current user is null, meaning the user is not logged in
// if they want to access the page, they need to be redirected in a way to log in
const { currentUser } = useSelector(mapState);
useEffect(() => {
//checks if the current user is null
if(!currentUser){
//redirect the user to the log in page
//we have access to history because of withRoute in withAuth.js
props.history.push('/login');
}
// eslint-disable-next-line react-hooks/exhaustive-deps
},[currentUser]); //whenever currentUser changes, it will run this code
return currentUser;
};
export default useAuth;

You can make use of local storage as previously mentioned in the comments:
When user logs in
localStorage.setItem('currentUserLogged', true);
And before if(!currentUser)
var currentUser = localStorage.getItem('currentUserLogged');
Please have a look into the following example
Otherwise I recommend you to take a look into Redux Subscribers where you can persist states like so:
store.subscribe(() => {
// store state
})

There are two ways through which you can authenticate your application by using local storage.
The first one is :
set a token value in local storage at the time of logging into your application
localStorage.setItem("auth_token", "any random generated token or any value");
you can use the componentDidMount() method. This method runs on the mounting of any component. you can check here if the value stored in local storage is present or not if it is present it means the user is logged in and can access your page and if not you can redirect the user to the login page.
componentDidMount = () => { if(!localStorage.getItem("auth_token")){ // redirect to login page } }
The second option to authenticate your application by making guards. You can create auth-guards and integrate those guards on your routes. Those guards will check the requirement before rendering each route. It will make your code clean and you do not need to put auth check on every component like the first option.
There are many other ways too for eg. if you are using redux you can use Persist storage or redux store for storing the value but more secure and easy to use.

Related

Firebase Authentication long loading time to verify user | Nextjs

I have a react application using firebase authentication. I have a global context that handles all functionality around the currently logged in user. This is my useEffect in said context:
useEffect(() => {
const unsubscribeAuth = firebase.auth.onAuthStateChanged(handleUser);
const unsubscribeToken = firebase.auth.onIdTokenChanged(handleUser);
return () => {
unsubscribeAuth();
unsubscribeToken();
};
}, []);
I have a <ProtectedRoute/> component that checks if the user is logged in. When I visit a protected route it's loading for quite some time, around 5 seconds before rendering the actual page.
ProtectedRoute component
function ProtectedRoute({ children }: Props) {
const router = useRouter();
const auth = useAuth();
if (auth.loading) { // Still loading (around 5 seconds!)
return ( <Spinner/>)
}
if (auth.user) {
// Accepted -> render children
return children;
} else {
// Redirect to sign in
router.push("/sign-in");
return null;
}
}
Is there any way to make this check way faster?
It takes around 0.5s if the user has not been logged in before, so I assume the reason it takes a long time is that firebase is confirming the saved token by making a request somewhere. But it shouldn't take that long, right?
I remember that I ran to the same problem and it was because of trying to get the token while the user is not authentificated yet. If it's the case you should verify first if the user is authentificated then try to recieve the token.

How to manage global states with React Query

i have a project that's using Nextjs and Supabase. I was using context API and now i'm trying to replace it for React Query, but i'm having a hard time doing it. First of all, can i replace context completely by React Query?
I created this hook to get the current user
export const getUser = async (): Promise<Profile> => {
const onFetch = await supabase.auth.getUser();
const userId = onFetch.data.user?.id;
let { data, error } = await supabase
.from("profiles")
.select()
.eq("id", userId)
.single();
return data;
};
export const useUser = () => {
return useQuery(["user"], () => getUser());
};
I'm not sure how to trigger it. When i was using context i was getting the user as this. If data, then it would redirect to the HomePage
let { data, error, status } = await supabase
.from("profiles")
.select()
.eq("id", id)
.single();
if (data) {
setUser(data);
return true;
}
Since i was getting the user before redirecting to any page, when i navigated to profile page, the user was already defined. How can i get the user before anything and keep this state? I suppose that once the user is already defined, at the profile component i can call useUser and just use it's data. But it's giving me undefined when i navigate to profile, i suppose that it's fetching again.
const { data, isLoading } = useUser();
But it's giving me undefined when i navigate to profile, i suppose that it's fetching again.
Once data is fetched when you call useUser, it will not be removed anymore (unless it can be garbage collected after it has been unused for some time). So if you do a client side navigation (that is not a full page reload) to another route, and you call useUser there again, you should get data back immediately, potentially with a background refetch, depending on your staleTime setting).
If you're still getting undefined, one likely error is that you are creating your QueryClient inside your app and it thus gets re-created, throwing the previous cache away. You're not showing how you do that so it's hard to say. Maybe have a look at these FAQs: https://tkdodo.eu/blog/react-query-fa-qs#2-the-queryclient-is-not-stable

How to configure between pages role control automation in Next.js?

enter image description here
every time the route information changes, it should get the user's role information and then check it before going to the page he wants to go to.
If the permissions of the page and the user role match, it should load the page and allow access.
If there is no access permission, it should redirect the user to the error page without loading the page.
I need an algorithm that will capture all pages in "user_app" in "Next.js" and manage access by providing control.
1- When the user wants to enter a page, I got the path information.
2- I got the data where the page permissions are defined in the process.env with the path information.
3- I got the user role information from the cookie/token.
4- I compared the role information with the page permissions. If the user has permission, they can enter the page, if not, they will be redirected to the 404 page.
Problem: This control structure works when the user enters the page he wants to go to. and it makes that page accessible for a very short time. (while the control function is running)... as I added in the picture, the new page should be checked before loading and after the permission is granted, the page data should be loaded and run.
note: this function is triggered in app_js every time the page changes.
import React from 'react';
import axios from 'axios';
import UserLogout from '../UserLogout';
import decrypted from '../crypto/decrypted';
import { useRouter } from 'next/router';
export default async function RolePageCheck() {
try {
const router = useRouter();
//kullanıcı hangi sayfada onu alıyoruz.
const path = router.pathname;
//public erişebilir sayfaları aldık
//sadece izinle girilebilen sayfaları aldık
const pageRoles = process.env.pageLinks[path];
const res = await axios.get(`${process.env.NEXT_PUBLIC_API_URL}/token/${"GET"}`).then((value) => {
//cookie var ise kullanıcı bilgilerini alıyoruz.
if(value.data.Acoount !== null && value.data.Acoount !== undefined && value.data.success === true){
const decryptedValue = decrypted(value.data.Acoount);
const userRole = decryptedValue.role;
if(pageRoles.includes(userRole) && ( !pageRoles.includes(0) || path==="/")){
return true;
}
else{
return router.replace("/404");
}
}
else if(pageRoles.includes(0)){
return true;
}
else{
return router.replace("/404");
}
});
} catch (error) {
return { success: false, message: error.message };
}
}
you can achieve this with router.events
useEffect(() => {
// on initial load - run auth check
authCheck(router.asPath);
// on route change start - hide page content by setting authorized to false
const hideContent = () => setAuthorized(false);
const logAction = () => {
console.log('routeChangeStart');
};
router.events.on('routeChangeStart', logAction);
router.events.on('routeChangeStart', hideContent);
// on route change complete - run auth check
router.events.on('routeChangeComplete', authCheck);
// unsubscribe from events in useEffect return function
return () => {
router.events.off('routeChangeStart', logAction);
router.events.off('routeChangeStart', hideContent);
router.events.off('routeChangeComplete', authCheck);
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);

Do I need to store isUserSignIn for componet rendering in Local Storage(React,Next,Firebase Auth)

(Firebase Google Signin + React(Next) )
If user sign in , setState to true.
And render the home component and welcome(login) component in index.js by state value.I made an application by storing that state in local storage and I think it is not a good practice. So if I don't use local storage there is a issue when user signed in and it should reach the home component but , user can also see welcome component before home component.
Logic index.js -> if userSignin (Home) : (Login)
Conditional Rendering
{isSignin ? (<>Home Component</>) : (<Welcome Component/>)}
state
const [isSignin, setisSignin] = useState(false);
changing state if user signin
useEffect(() => {
onAuthStateChanged(auth, (user) => {
if (user) {
setisSignin(true);
console.log(user);
}else {
setisSignin(false);
}
});
}, []);
firebase auto use localstorage when u signin an account. please check it in your browser localstorage. You don't need to store it by yourself. Then, when user revisit that website from the same web browser, firebase auto login (unless he did not logout). However, that auto login needs some amount of time. you need to do something during that amount of time. after that time, isSignIn will be true. I suggest something like this.
const [isSignIn, setSignIn] = useState(null);
useEffect(()=> listener_to_authstate_change() ,[])
if (isSignIn === null) return null; // something you like to show example - <Loading />
if (isSignIn === false) return <SignIn Component />
if (isSignIn === true) return <Home Component />

How to wait for auth state to load on the client before fetching data

Recently I stumbled across the useAuth and useRequireAuth hooks: https://usehooks.com/useRequireAuth/. They are incredibly useful when it comes to client-side authentication. However, what's the correct way for waiting until auth data is available to fetch some other data? I've come up with the following code:
const Page = () => {
// makes sure user is authenticated but asynchronously, redirects if not authenticated, short screen-flash
useRequireAuth()
// actual user object in state, will be updated when firebase auth state changes
const user = useStoreState((state) => state.user.user);
if (!user) {
return <div>Loading</div>
}
useEffect(() => {
if (user) {
fetchSomeDataThatNeedsAuth();
}
}, [user]);
return (
<h1>Username is: {user.name}</h1>
)
}
Is this a "good" way to do it or can this be improved somehow? It feels very verbose and needs to be repeated for every component that needs auth.
This looks fine to me. The thing you could improve is that your useRequireAuth() could return the user, but that's up to you.
Additionally, you probably should check if user is defined before rendering user.name.

Resources