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.
Related
I am using Firebase, and after login to watch if user is logged in using the following block;
useEffect(() => {
const checkAuthToken = () => {
const token = sessionStorage.getItem('auth-token');
if (token) {
setIsAuthenticated(true);
} else {
setIsAuthenticated(false);
}
}
window.addEventListener('storage', checkAuthToken);
return () => {
window.removeEventListener('storage', checkAuthToken);
}
}, [])
It works without issue until I refresh the page even sessionStorage data is still on the browser memory.
The idiomatic way to respond to sign in with Firebase is to listen for auth state changes, rather than reading something from local storage yourself.
I recommend sticking to that approach, which is shown in the first snippet in the documentation on getting the current user.
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
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.
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.
I have an app that I do not need a scenario where there is no user. Once the user lands on the main page a user is created for them anonymously. If they are a user with credentials and they are signed in when they sign out a user anonymous should be created again till they sign in again.
I took a HOC component that was working until I was ready to go into production then I noticed a bug in code where a user is created twice when they land on the index page.
This is my HOC that I wrap my index page and every other page with
const AuthHoc = (Component) => {
return class extends React.Component {
constructor(props) {
super(props);
}
componentDidMount() {
console.log("mounted") /////this logs twice for some reason I dont know why
auth.onAuthStateChanged((user) => {
if (user) {
console.log(user.uid);
} else {
console.log("no user");
var user = auth.currentUser;
if (user) {
// User is signed in.
console.log("there is already a suer")
} else {
// No user is signed in.
auth.signInAnonymously().then(async (cred) => {
uid=cred.user.uid
console.log("user created with id", cred.user.uid);
});
}
}
});
render() {
return <Component {...this.props} />;
}
};
};
export default AuthHoc;
and wrapping my index page as
export default connect(mapStateToProps)(AuthHoc(Index));
Every log in this code is called twice. The component mounts... twice (I don't know why) it finds no user, as it should, creates a user and logs the new user id. The onAuthStateChanged callback fires and logs the uid in the if user block of code. Then the problem comes where it reruns the whole process a second time, it creates a user again ignoring the conditionals.
How best can I improve this code. I need to reuse it across the entire app