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
Related
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.
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.
so I might have difficulty explaining this issue I am having, which I am not able to reproduce consistently. I have a React app on which I am using react-redux-firebase and that I thought I was successfully implementing to keep track of the user session.
My App.js file has the following bit or routing code as a sample (using react-router-dom):
<Route
path="/signin"
render={() => {
if (!isLoaded(this.props.auth)) {
return null;
} else if (!isEmpty(this.props.auth)) {
return <Redirect to="/posts" />;
}
return <Signin />;
}}
/>
This works correctly. I go to Signin component when user is not logged in or Posts when user is logged in. In Signin component I have this bit of logic that happens:
// sign the user in
this.props.firebase.login({
email: user.email,
password: user.password
}).then(response => {
// detect the user's geolocation on login and save
if (isLoaded(this.props.auth)) {
const navigator = new Navigator();
navigator.setGeoLocation(this.props.firebase);
}
// redirect user to home or somewhere
this.props.history.push('posts');
})
I am importing isLoaded like so:
import { firebaseConnect, isLoaded } from 'react-redux-firebase';
the condtional works fine, the user is logged in and then the isLoaded conditional happens - this is where the problem arises. With isLoaded true I would assume that the user and all the redux-firestore user properties are ready for use....but that is sometimes not the case. In navigator.setGeoLocation call I have this:
setGeoLocation(propsFirebase, cb) {
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition((position) => {
propsFirebase.auth().currentUser
.getIdToken(/* forceRefresh */ true)
.then((idToken) => {
...
});
}
)
}
}
At this point, propsFirebase.auth().currentUser is sometimes null (this originates in the parameter passed in navigator.setGeoLocation(this.props.firebase);). If I try the signin in all over again, then it works. I am not able to find any consistent way of reproducing.
I have noticed this in other components too. I am not sure if this is an issue that happens when my computer goes to sleep and I should restart the whole React process or what? has anyone seen similar issues? If so, what could I possibly be missing during checking user state in the routing?
If more code is necessary, let me know...
currentUser will be null with the user is not signed in. It will also be null when a page first loads, before the user's token has been loaded and a User object is available. You should use an auth state observer to get the User object if you want to act immediately after it is loaded asynchronously after page load.
firebase.auth().onAuthStateChanged((user) => {
if (user) {
// User is signed in, see docs for a list of available properties
// https://firebase.google.com/docs/reference/js/firebase.User
var uid = user.uid;
// ...
} else {
// User is signed out
// ...
}
});
You might also want to read for more detail: Why is my currentUser == null in Firebase Auth?
I'm making an app in React and I want to users have the option to create an account using their email/password as auth factors. However, I want the option for two different user types. For example, a client and physician. So that when a user logs in they are taken to unique profiles (ie: clientProfile.js or physicianProfile.js) to that user type. Is this a db item? A firebase Auth token? Any advice would be great!
When a user registers they'll select their role from a select dropdown (or whatever UI element you choose): Client or Physician. You'll save this to the database.
When the user logs in you'll return their role along with any other relevant user data the front-end needs to handle the user.
When you render the profile you'll check their role and return the appropriate component: clientProfile.js or physicianProfile.js
Your component may look something like this:
class UserProfile extends Component {
constructor(props) {
super(props);
this.state = {
user: null
};
}
componentDidMount() {
/* fetch user from database */
this.setState({user: response.user});
}
render() {
const { user } = this.state;
if (user === null) {
return <Loading/>;
}
if (user.role === 'physician') {
return <PhysicianProfile user={user}/>;
}
return <ClientProfile user={user}/>;
}
}
Assume the following situation: As a logged-in user, you're accessing your own profile page. The header menu will show your avatar as a logged-in user, and the rest of the page will show your user public information.
You got a list of users from the api, and you need to handle data from 2 of these users: the logged-in user and the user which profile page is being accessed (in the situation above, it's actually the same user).
Duplicating the data in store.users, store.currentUser and store.myUser would be a very bad idea (need to sync these 3 occurrences of the data everytime it gets modified). But I do not want my components using the current or logged in user data to be re-rendered everytime some lambda user data gets modified in the list of users.
How would you proceed in order to avoid data duplication, speed up data handling time (avoid costly list operations like filter/find), and avoid unnecessary re-rendering ?
After some thinking, I'm thinking of implementing it this way,
Data are stored as follow:
store = {
users: {
1: {
name: 'John',
},
},
currentUser: 1, // user which profile page is being accessed
myUser: 1, // logged in user
};
Now, the UserDetails component can be connected as follow:
#connect(state => {user: state[state.currentUser]})
export default class UserDetails extends Component {
render() {
return <div>User profile: {this.props.user.name}</div>;
}
}
and the page header using the logged in user data can be connected as follow:
#connect(state => {user: state[state.myUser]})
export default class HeaderMenu extends Component {
render() {
return <div>Logged-in user: {this.props.user.name}</div>;
}
}
But I'm not convinced that this is an ideal solution. Will have to test whether the re-rendering is truly avoided or not.
Your thoughts are very welcome!
I would store the details of the logged in user in the Redux store. Whatever data necessary to manage the user's session and render the persistent header. For other user profiles I'd load the data on-the-fly from the backend.
For example, if you had a route /profile/:id and this was the URL foo.com/profile/99
class Profile extends Component {
constructor(props) {
super(props);
this.state = {
user: null,
loading: true
};
}
componentDidMount() {
api.get('/getUser', {userId: this.props.match.params.id}, response => {
this.setState({
user: response.user,
loading: false
});
});
}
render() {
return (
...
);
}
}