I have two users, one is admin and the other client, so when I login as admin I want to display admin screen immediately and also when I login as client display client screen but login as admin I still can see client screen for few seconds then take me to the admin screen and when I logout I go back to client screen not login screen
Login screen code:
const SignIn = ({navigation}) => {
const [email, setEmail] = React.useState('')
const [password, setPassword] = React.useState('')
useEffect(()=>{
const unsubscribe = auth.onAuthStateChanged(user =>{
if(user){
navigation.navigate('Tabs')
setEmail()
setPassword()
}
})
return unsubscribe
},[])
App screen code:
const App = ({navigation}) => {
const [user, setUser] = React.useState(null) // This user
const [users, setUsers] = React.useState([]) // Other Users
useEffect(() => {
const unsubscribe = auth.onAuthStateChanged(async user => {
if (user) {
const userData = await db.collection("users").doc(user.uid).get();
setUser(userData.data());
} else {
setUser(null);
}
});
return () => unsubscribe();
}, [])
return (
<NavigationContainer independent={true}>
{user?.role === 'admin'? (<AdminNavi />):(<UserNavi/>)}
</NavigationContainer>
);
}
export default App;
I feel like I'm not structure the code correctly, because If you look at app screen code I'm repeating the use Effect again and I put the condition between Container Navigator. Please guys anyone of you can Re-structure my code is highly appreciated.
My best guess is that those few seconds are when you're loading the user data from Firestore. This may take some time, and you'll need to decide what you want to display during that time.
The simplest fix is to set the user to null while you're loading their data from Firestore:
useEffect(() => {
const unsubscribe = auth.onAuthStateChanged(async user => {
if (user) {
setUser(null); // π
const userData = await db.collection("users").doc(user.uid).get();
setUser(userData.data());
} else {
setUser(null);
}
});
return () => unsubscribe();
}, [])
This will work, but causes the app to temporarily show the login screen while you're loading the data. If that is not acceptable, you'll want to introduce another state variable for while you're loading the user profile from Firestore:
useEffect(() => {
const unsubscribe = auth.onAuthStateChanged(async user => {
if (user) {
setLoadingUser(true); // π
const userData = await db.collection("users").doc(user.uid).get();
setLoadingUser(false); // π
setUser(userData.data());
} else {
setUser(null);
}
});
return () => unsubscribe();
}, [])
And then you'd use the loadUser state property to render a "Loading user profile..." type indicator.
Related
I am trying to get my sign-up form to work using Firebase createUserWithEmailAndPassword and updateProfile functions in my React App. The sign-in function works (right panel), and I am able to see users in Firebase, however, when I try to create the displayName (left panel) I'm running into a reference issue.
My AuthContext provider has my signup function written like so:
export const useAuth = () => {
return useContext(AuthContext);
};
const AuthProvider = ({ children }) => {
const [currentUser, setCurrentUser] = useState();
const [loading, setLoading] = useState(true);
const signup = (email, password) => {
return auth.createUserWithEmailAndPassword(email, password)
};
useEffect(() => {
const unsubscribe = auth.onAuthStateChanged((user) => {
setCurrentUser(user);
setLoading(false);
});
return unsubscribe;
}, []);
const value = {
currentUser,
signup,
};
return (
<AuthContext.Provider value={value}>
{!loading && children}
</AuthContext.Provider>
);
};
I imported my useAuth function to my SignUp component and placed it inside my handleSubmit:
const { signup } = useAuth();
const handleSubmit = async (e) => {
e.preventDefault();
if (passwordRef.current.value !== passwordConfRef.current.value) {
return setError("Please make sure passwords match.");
}
try {
setError("");
setLoading(true);
const user = await signup(
emailRef.current.value,
passwordRef.current.value
)
await updateProfile(user, {
displayName: usernameRef,
uid: user.user.uid,
})
// console.log(user);
navigate(`/dashboard/${user.user.uid}`);
} catch (e) {
setError("Something went wrong, please try again.");
console.log(e.message)
}
setLoading(false);
};
When the following console.log is run on the browser the following populates in the console:
find the error below
"Cannot access 'user' before initialization"
but I don't know what user reference it's referring to.
I read the following docs in reference to creating more login credentials for firebase:
https://firebase.google.com/docs/reference/js/v8/firebase.User#updateprofile
https://github.com/firebase/firebase-functions/issues/95
I originally had the updateProfie in a .then fucntion, however after some assistance I changed it await and now I'm getting a new error:
userInternal.getIdToken is not a function
The Promise chain isn't correct in the handleSubmit handler. The then-able should take a function instead of the result of immediately calling updateProfile. The user parameter likely hasn't been instantiated yet. In other words updateProfile is called when the component is rendered instead of when handleSubmit is called. You are also mixing the async/await and Promise-chain patterns. Use one or the other.
const handleSubmit = async (e) => {
e.preventDefault();
if (passwordRef.current.value !== passwordConfRef.current.value) {
setError("Please make sure passwords match.");
return;
}
try {
setError("");
setLoading(true);
await signup(
emailRef.current.value,
passwordRef.current.value,
);
navigate(`/dashboard/${user.user.id}`);
} catch (e) {
console.warn(e));
setError("Something went wrong, please try again.");
} finally {
setLoading(false);
}
};
You can call separately the updateProfile function when the currentUser state updates.
const AuthProvider = ({ children }) => {
const [currentUser, setCurrentUser] = useState();
const [loading, setLoading] = useState(true);
const signup = (email, password) => {
return auth.createUserWithEmailAndPassword(email, password)
};
useEffect(() => {
const unsubscribe = auth.onAuthStateChanged((user) => {
setCurrentUser(user);
setLoading(false);
});
return unsubscribe;
}, []);
useEffect(() => {
if (currentUser) {
updateProfile({
/* profile properties you want to update */
});
}
}, [currentUser]);
const value = {
currentUser,
signup,
};
return (
<AuthContext.Provider value={value}>
{!loading && children}
</AuthContext.Provider>
);
};
I'm using Firebase Web version 9 in a react app. I have a sign-up form with first name, email, and password inputs. When the form is submitted, I need to create an authorized user with Firebase and immediately update that new user's first name and profile picture.
I'm using two Firebase auth functions - createUserWithEmailAndPassword() and updateProfile(). A new user is always created when the form is submitted, but the profile updates only happen once in a while. I haven't been able to pinpoint the cases when the profile update is successful.
Any ideas on what I'm missing? Would love some feedback and guidance. Thank you!
Here's the code for the sign-up page.
import { useState, useEffect } from 'react';
import { Link, useNavigate } from 'react-router-dom';
import {
StyledHeader,
StyledFooter,
StyledDropdown,
StyledForm,
} from '../styles';
import {
getAuth,
createUserWithEmailAndPassword,
updateProfile,
} from 'firebase/auth';
import { ShortFooter, LanguageSelect, Form } from '../components';
import { logo, p1, p2, p3, p4, p5 } from '../assets';
const SignUp = ({ children }) => {
const [firstName, setfirstName] = useState('');
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [isFirstNameEmpty, setIsFirstNameEmpty] = useState(true);
const [isEmailEmpty, setIsEmailEmpty] = useState(true);
const [isPasswordEmpty, setIsPasswordEmpty] = useState(true);
const [error, setError] = useState('');
const [checked, setChecked] = useState(true);
const [isShown, setIsShown] = useState(true);
const isInvalid = firstName === '' || email === '' || password === '';
const navigate = useNavigate();
const userProfileImgs = [p1, p2, p3, p4, p5];
useEffect(() => {
setTimeout(() => {
// ποΈ scroll to top on page load
window.scrollTo({ top: 0, left: 0, behavior: 'smooth' });
}, 100);
}, []);
const handlefirstNameChange = (firstName) => {
if (firstName.length !== 0) {
setIsFirstNameEmpty(false);
} else {
setIsFirstNameEmpty(true);
}
};
const handleEmailChange = (email) => {
if (email.length !== 0) {
setIsEmailEmpty(false);
} else {
setIsEmailEmpty(true);
}
};
const handlePasswordChange = (password) => {
if (password.length !== 0) {
setIsPasswordEmpty(false);
} else {
setIsPasswordEmpty(true);
}
};
const handleCheckbox = () => {
setChecked((checked) => !checked);
};
const handleLearnMore = (e) => {
e.preventDefault();
setIsShown((isShown) => !isShown);
};
const handleSignUp = async (e) => {
e.preventDefault();
try {
// firebase work!
const auth = getAuth();
let { user } = await createUserWithEmailAndPassword(
auth,
email,
password
);
console.debug(`User ${user.uid} created`);
// updating the user's profile with their first name and a random profile image
await updateProfile(user, {
displayName: firstName,
photoURL:
userProfileImgs[Math.floor(Math.random() * userProfileImgs.length)],
});
console.debug(`User profile updated`);
// navigate to the profile page
navigate('/profile');
} catch (e) {
if (e.message === 'Firebase: Error (auth/email-already-in-use).') {
setError('That email is already in use, please try again.');
}
}
};
return (
<>
<StyledHeader height="120">
<div className="header__background">
<div className="header__frame">
<div>
<Link to="/">
<img className="header__logo" src={logo} alt="Netflix Logo" />
</Link>
</div>
</div>
<form onSubmit={handleSignUp} className="form__container">
<StyledForm padding="20px 68px 40px">
<Form
error={error}
isEmailEmpty={isEmailEmpty}
email={email}
setEmail={setEmail}
handleEmailChange={handleEmailChange}
isPasswordEmpty={isPasswordEmpty}
password={password}
setPassword={setPassword}
handlePasswordChange={handlePasswordChange}
isInvalid={isInvalid}
checked={checked}
handleCheckbox={handleCheckbox}
handleLearnMore={handleLearnMore}
isShown={isShown}
isFirstNameEmpty={isFirstNameEmpty}
firstName={firstName}
setfirstName={setfirstName}
handlefirstNameChange={handlefirstNameChange}
method="post"
/>
</StyledForm>
</form>
<div className="signIn__footer-container">
<div className="signIn__footer-background">
<StyledFooter
backgroundColor="transparent"
padding="10px 70px 40px 70px"
>
<ShortFooter>
<StyledDropdown>
<LanguageSelect />
</StyledDropdown>
</ShortFooter>
</StyledFooter>
</div>
</div>
</div>
{children}
</StyledHeader>
</>
);
};
export default SignUp;
This is the error I'm getting in the console.
Error
Based on how my app was set up I ultimately decided to remove the photoURL property altogether since it could be defined elsewhere in the app. After testing, only one of my images worked. This is why I experienced the full profile updates working out sometimes since random images were selected each time a new user signs up. I ended up sticking wth this image as the default image.
As #adsy mentioned, my profile updates weren't happening consistently because certain profile images I had exceeded the allowed input body length for this property. The example in the Docs shows a link with 43 characters but no other information. The path to the profile images in the question are much shorter than this so I still need some understanding.
After researching a bit, I didn't come across the maximum limit set. It's now something to contact Google Developer's support team about. If anyone has the information for the allowed input body length for this property, feel free to share it on this post.
Thanks!
const handleSignUp = async (e) => {
e.preventDefault();
try {
// firebase work!
const auth = getAuth();
let { user } = await createUserWithEmailAndPassword(
auth,
email,
password
);
console.debug(`User ${user.uid} created`);
// updating the user's profile with their first name
await updateProfile(user, {
displayName: firstName,
});
console.debug(`User profile updated`);
// navigate to the profile page
navigate('/profile');
} catch (e) {
if (e.message === 'Firebase: Error (auth/email-already-in-use).') {
setError('That email is already in use, please try again.');
}
}
};
I have this component in which I want to update a state and make an array with al the users connected in a room from an incoming msg from socket.on:
export default function ChatRoom() {
const [users, setUsers] = useState([]);
const { state } = useLocation(); //to get data from <Home/> component
socket.on("message", (msg) => console.log(msg));
useEffect(() => {
if (state.connected) {
socket.on("userconnected", (msg) => {
setUsers((old) => [...old, msg]);
console.log(state.username);
});
}
}, [state.connected, users]);
The username is coming from another component state in which I send a msg to the server:
export default function Home() {
const [username, setUsername] = useState("");
const [room, setRoom] = useState("");
const [connected, setConnected] = useState(false);
useEffect(() => {
if (connected && room && username) {
navigate("/chatroom", {
state: { username, room, connected },
});
}
}, [connected, room, username]);
const navigate = useNavigate();
const handleClick = (e) => {
e.preventDefault();
socket.emit("connected", username);
setConnected(true);
};
The problem is that I cant update the state with setUsers. I think it has to do with an async problem but I canΒ΄t figure it out. Basically I need an array with all the users that are connected.
I'm using native-CLI to build a react-native app. Everything is working fine, but when I log in to the user through API, I receive token and other values. I set them in AsyncStorage and it should jump to the buyer dashboard screen but it can't but when I refresh the app then it goes to the buyer dashboard. Basically, it's not refreshing the app.js after pressing the login button it should refresh the app.js also.
LoginButtonCode
const login = async () => {
if (name != '' && password != '') {
const login_Credentials = new FormData();
login_Credentials.append('username', name);
login_Credentials.append('password', password);
setPress(true)
await axios({
method: 'POST',
url: api + 'login/',
data: login_Credentials,
headers: { 'Content-Type': 'multipart/form-data' }
}).then(async function (response) {
if (response.data.Success == true) {
const token = response.data.token.toString();
const super_user_status = response.data.super_user_status.toString();
const isLoggedIn = "1"
console.log('Logged In and set Storgae')
await AsyncStorage.multiSet([['isLoggedIn',isLoggedIn],['token', token], ['super_user_status', super_user_status]])
setName('')
setPassword('')
setPress(false)
} else if (response.data.Success == false) {
setPress(false)
setErrorMsg(response.data.Error)
setName('')
setPassword('')
}
}).catch(function (response) {
console.log(response, "error");
})
} else {
setErrorMsg('Please Enter Username/Password.')
}
}
app.js
const App = () => {
const [user, setUser] = useState(false)
const [role, setRole] = useState('seller')
useEffect(()=>{
getKeysData(dataKeys)
},[])
const dataKeys = ['token', 'super_user_status', 'isLoggedIn'];
const getKeysData = async (keys) => {
const stores = await AsyncStorage.multiGet(keys);
const aData = stores.map(([key, value]) => ({ [key]: value }))
const token = aData[0]['token']
const super_user_status = aData[1]['super_user_status']
const isLoggedIn = aData[2]['isLoggedIn']
console.log('token',token)
console.log('SuperUser', super_user_status)
console.log('Log',isLoggedIn)
if(isLoggedIn == '1'){
setUser(true)
}else{
setUser(false)
}
}
//AsyncStorage.clear()
return (
<NavigationContainer>
{ user == false ?
<AuthStackScreen />
:
<BuyerDashboardStackScreens />
}
</NavigationContainer>
);
};
I use AsyncStorage to update the state in app.js.
As you know the AsyncStorage is async, the user is false at the beginning and the state is not loaded to the state. You should set a loading state and until the state is not loaded from the AsyncStorage you show a splash screen. When you login, set the user state from the response.
const [user, setUser] = useState(false)
const [role, setRole] = useState('seller')
const [isLoading, setIsLoading] = useState(true)
...
const getKeysData =() => {
// wait for storage data
// setUser
setIsLoading(false)
}
...
if(isLoading) return <Loader />
return <Navigation.../>
I have a component I want to redirect to using react router. How can I set the state of the new component with a string that I chose on the original component? All of my redirects using react router are working and this component that is being redirected to isn't working. It is a html button when clicked should render this new components with initial data.
const Posts = (props) => {
const dispatch = useDispatch();
const getProfile = async (member) => {
console.log(member)
props.history.push('/member', { user: member});
console.log('----------- member------------')
}
const socialNetworkContract = useSelector((state) => state.socialNetworkContract)
return (
<div>
{socialNetworkContract.posts.map((p, index) => {
return <tr key={index}>
<button onClick={() => getProfile(p.publisher)}>Profile</button>
</tr>})}
</div>
)
}
export default Posts;
This is the component I am trying to redirect to on click.
const Member = (props)=> {
const [user, setUser] = useState({});
const { state } = this.props.history.location;
const socialNetworkContract = useSelector((state) => state.socialNetworkContract)
useEffect(async()=>{
try {
await setUser(state.user)
console.log(user)
console.log(user)
const p = await incidentsInstance.usersProfile(state.user, { from: accounts[0] });
const a = await snInstance.getUsersPosts(state.user, { from: accounts[0] });
} catch (e) {
console.error(e)
}
}, [])
I get the following error in the console.
TypeError: Cannot read property 'props' of undefined
Member
src/components/profiles/member.js:16
13 | const [posts, setPosts] = useState([]);
14 | const [snInstance, setsnInstance] = useState({});
15 | const [accounts, setsAccounts] = useState({});
> 16 | const { state } = this.props.history.location;
If you need to send some route state then the push method takes an object.
const getProfile = (member) => {
console.log(member)
props.history.push({
pathname: '/member',
state: {
user: member,
},
});
console.log('----------- member------------')
}
Additionally, Member is a functional component, so there is no this, just use the props object.
The route state is on the location prop, not the history object.
const Member = (props)=> {
const [user, setUser] = useState({});
const { state } = props.location;
// access state.user
Also additionally, useEffect callbacks can't be async as these imperatively return a Promise, interpreted as an effect cleanup function. You should declare an internal async function to invoke. On top of this, the setuser function isn't async so it can't be awaited on.
The following is what I think should be the effects for populating the user state and issuing side-effects:
// update user state when route state updates
useEffect(() => {
if (state && state.user) {
setUser(state.user);
}
}, [state]);
// run effect when user state updates
useEffect(() => {
const doEffects = async () => {
try {
const p = await incidentsInstance.usersProfile(state.user, { from: accounts[0] });
const a = await snInstance.getUsersPosts(state.user, { from: accounts[0] });
} catch (e) {
console.error(e)
}
}
doEffects();
}, [user]);