user.displayName not showing on react firebase app - reactjs

const [user, setUser] = useState({});
const [pass, setPass] = useState('')
const [name, setName] = useState('')
const [isLoading, setIsLoading] = useState(true)
const auth = getAuth();
const inputHandler = e => {
setUser(e?.target.value)
}
const passHandler = e => {
setPass(e?.target.value)
}
const nameHandler = e => {
setName(e?.target.value)
}
const toggleLogin = event => {
setIsLogIn(!event.target.checked);
}
const signUpHandler = (e) => {
signUp(user, pass)
.then(result => {
setUserName();
history.push(url)
// console.log(url)
})
.finally(() => {
setIsLoading(false)
})
.catch((error) => {
setError(error.message)
// ..
});
e.preventDefault()
}
const signUp = (user, pass) => {
setIsLoading(true)
return createUserWithEmailAndPassword(auth, user, pass)
}
useEffect(() => {
onAuthStateChanged(auth, (user) => {
if (user) {
setUser(user)
// console.log("auth changed",user.email)
} else {
setUser({})
}
setIsLoading(false)
});
}, [auth])
const setUserName = () => {
updateProfile(auth.currentUser, {
displayName: name
});
Before displayName property being updated its redirecting to the route it came from. Is this happening for asynchronous nature?
I'm trying to set the displayName property on the navbar.displayName is getting set but not showing on ui, but showing after when I refresh the page. How can I fix this issue?

Related

Why do I have to refresh my app to display the right screen when I switch the user?

I have two users (admin and user). When I log in as user I display user home screen and when I log out and log in as admin I still see user home screen until I refresh my app, then I can see the admin home screen.
Thank you in advance.
here is my code:
import { auth, db } from '../../firebase';
const Home = ({navigation})=>{
const [modalVisible, setModalVisible]=useState(false)
const [formType, setFormType] = React.useState("")
const [user, setUser] = useState(null) // This user
const [users, setUsers] = useState([]) // Other Users
useEffect(() => {
db.collection("users").doc(auth?.currentUser.uid).get()
.then(user => {
setUser(user.data())
})
}, [])
useEffect(() => {
if (user)
db.collection("users").where("role", "==", (user?.role === "admin" ? 'admin' : null))
.onSnapshot(users => {
if (!users.empty) {
const USERS = []
users.forEach(user => {
USERS.push(user.data())
})
setUsers(USERS)
}
})
}, [user])
const handleSignOut = ()=>{
auth
.signOut()
.then(()=>{
navigation.navigate('SignIn')
})
.catch(error => alert(error.message))
}
return(
<View>
{user?.role === 'admin'? <AdminScreen />:<UserScreen/>}
</View>
)
The issue is that the user state is not being updated when the user logs in as a different account. To solve this issue, I have added a listener to the auth object to detect changes in the current user and updating the user state when the current user changes.
import { auth, db } from '../../firebase';
const Home = ({navigation})=>{
const [modalVisible, setModalVisible]=useState(false)
const [formType, setFormType] = React.useState("")
const [user, setUser] = useState(null) // This user
const [users, setUsers] = 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();
}, [])
useEffect(() => {
if (user) {
const unsubscribe = db.collection("users").where("role", "==", (user?.role === "admin" ? 'admin' : null))
.onSnapshot(users => {
if (!users.empty) {
const USERS = []
users.forEach(user => {
USERS.push(user.data())
})
setUsers(USERS)
}
});
return () => unsubscribe();
}
}, [user])
const handleSignOut = ()=>{
auth
.signOut()
.then(()=>{
navigation.navigate('SignIn')
})
.catch(error => alert(error.message))
}
return(
<View>
{user?.role === 'admin'? <AdminScreen />:<UserScreen/>}
</View>
)

how to auto delete data after new data has been uploaded on firebase on react?

When a user updates their profile photo I want old data to be overwritten on firestore. I'm using firebase storage to store photos and upload firebase URL to restore database so
I tried filtering in on the front end side but I have multiple users to filter and there are a lot of duplicates
here is whole functionality of uploading data to firestore storage then updating firestore db and then pulling data with use
const [userImg, setUserImg] = useState()
const [image, setImage] = useState(null)
const [htlmImg, setHtmlImg] = useState(null)
const [url, setUrl] = useState(null)
const [userName, setUserName] = useState(null)
const [sureLoading, setSureLoading] = useState(false)
const [photoEdit, setPhotoEdit] = useState(false)
const handleImageChange = (e) => {
if (e.target.files[0]) {
setImage(e.target.files[0])
setHtmlImg(URL.createObjectURL(e.target.files[0]))
}
}
const uploadImg = () => {
const imageRef = ref(storage, `image${user.uid}`)
uploadBytes(imageRef, image)
.then(() => {
getDownloadURL(imageRef)
.then((url) => {
setUrl(url)
})
.catch((error) => {
console.log(error.message, 'error getting the image url')
})
setImage(null)
})
.catch((error) => {
console.log(error.message)
})
setSureLoading(true)
}
const handlePfpSubmit = async () => {
const { uid } = user
if (url !== null) {
try {
await addDoc(collection(db, 'user'), {
pfp: url,
userName,
uid,
timestamp: serverTimestamp(),
time: Date(),
})
if (!photoEdit) {
navigate('/test')
}
console.log('data send')
} catch (err) {
console.log(err)
}
}
}
const [displayName, setDisplayName] = useState(null)
const [displayPhoto, setDisplayPhoto] = useState(null)
const [userProfiles, setUserProfiles] = useState(null)
useEffect(() => {
const q = query(collection(db, 'user'),
orderBy('timestamp')).update()
const unsub = onSnapshot(q, (querySnapShot) => {
let photo = []
querySnapShot.forEach((doc) => {
photo.push({ ...doc.data(), id: doc.id })
})
console.log(photo)
console.log('data resived')
let userUid = photo
.filter((item) => {
if (user.uid === item.uid) {
return item.uid
}
})
.map((item) => {
const { pfp } = item
return pfp
})
setDisplayPhoto(
userUid.filter((val, index) => {
if (userUid.length - 1 <= index) {
return val
}
}),
)
let userUidName = photo
.filter((item) => {
if (user.uid === item.uid) {
return item.uid
}
})
.map((item) => {
const { userName } = item
return userName
})
let photoFilter = userUidName.filter((val, index) => {
if (userUidName.length - 1 <= index) {
return val
}
})
setDisplayName(photoFilter)
setUserProfiles(photo)
console.log(displayPhoto)
})
console.log('re render ? ')
return () => unsub()
}, [user])

Couldn't add current user after signing up without reloading (Next JS + Firebase)

I am using the Auth Provider to manage my Firebase auth information. I want to be able to use currentUser as soon as I sign up, but it won't set without reloading.
I tried to setCurrentUser out of the Auth Provider and set it, but I could not get it to work either.
contexts/Auth.tsx
const AuthContext = createContext<IAuthContext>(null!)
export const AuthProvider = ({
children,
}: {
children: ReactNode
}) => {
const [currentFBUser, setCurrentFBUser] = useState<firebase.User | null>(null)
const [currentUser, setCurrentUser] = useState<any>(null)
const [isLoading, setIsLoading] = useState<boolean>(true)
const { update } = useIntercom()
/**
* SUBSCRIBE user auth state from firebase
*/
useEffect(() => {
const unsubscribe = auth.onAuthStateChanged(async (user) => {
if (!user) {
setCurrentFBUser(null)
setIsLoading(false)
return
}
await setCurrentFBUser(user)
const storeUser = await userRepository.findById(user.uid)
if (!storeUser) {
setCurrentUser(null)
setIsLoading(false)
return
}
await setCurrentUser(storeUser)
/* UPDATE Intercom props */
if(currentUser) {
update({
name: currentUser.name,
email: currentUser.email
})
}
setIsLoading(false)
return () => {
unsubscribe()
}
})
}, [])
const logout = useCallback(() => {
const auth = getAuth();
signOut(auth).then(() => {
window.location.reload()
}).catch((err) => {
toast.error(err.message)
});
}, [])
return (
<AuthContext.Provider value={{
currentFBUser,
currentUser,
setCurrentUser,
isLoading,
logout,
}}>
{children}
</AuthContext.Provider>
)
}
export const useAuthContext = () => {
const context = useContext(AuthContext)
if (!context) {
throw new Error('useAuth must be used within the AuthProvider')
}
return context
}
signup.tsx
...
const { currentFBUser, isLoading, setCurrentUser } = useAuthContext()
const signup = handleSubmit(
async (data) => {
if (data.password != data.confirmPassword) {
toast.error('Your password is not matched!')
return
}
const auth = getAuth()
createUserWithEmailAndPassword(auth, data.email, data.password)
.then((userCredential) => {
const auth = userCredential.user
if (!auth) return
const { email, uid } = auth
if (!email) return
const user = userRepository.findOrCreate(email, uid)
setCurrentUser(user)
})
.catch((err) => {
toast.error(err.message)
});
},
(err: any) => {
toast.error(err.message)
},
)
...
Try to call again getAuth() instead of using the response from createUserWithEmailAndPassword
const { currentFBUser, isLoading, setCurrentUser } = useAuthContext()
const signup = handleSubmit(
async (data) => {
if (data.password != data.confirmPassword) {
toast.error('Your password is not matched!')
return
}
const auth = getAuth()
createUserWithEmailAndPassword(auth, data.email, data.password)
.then((userCredential) => {
// const auth = userCredential.user
const auth = getAuth().currentUser
if (!auth) return
const { email, uid } = auth
if (!email) return
const user = userRepository.findOrCreate(email, uid)
setCurrentUser(user)
})
.catch((err) => {
toast.error(err.message)
});
},
(err: any) => {
toast.error(err.message)
},
)

Getting additional data in firebase/auth - onAuthStateChanged

I want to get extra data from a users collection in firestore when user loggs in. I do this in a useEffect function in a AuthContext. This is my code:
useEffect(() => {
const unsubscribe = onAuthStateChanged(auth, (user) => {
const fetchUserData = async () => {
if (!user) {
setCurrentUser(null);
setLoading(false);
return;
}
const userData = await fetchUserDataFromFirestore(user.uid);
setCurrentUser({ ...user, ...userData });
setLoading(false);
};
fetchUserData();
});
return unsubscribe;
}, [currentUser]);
This kind of works as I do get the data but messages are piling up in the console as can be seen in my screenshot:
The fetchUserDataFromFirestore function is implemented like this:
export const fetchUserDataFromFirestore = async (id) => {
const docRef = doc(db, "users", id);
const docSnap = await getDoc(docRef);
if (docSnap.exists) {
const userData = docSnap.data();
return userData;
}
return null;
};
What can I do about this?
For future reference this is how I did it
const [uid, setUid] = useState(null)
useEffect(() => {
const unsubscribe = onAuthStateChanged(auth, async (user) => {
if (user) {
setUid(user.uid)
} else {
setUid(null)
}
})
return () => {
unsubscribe()
}
}, [])
// set currentUser state
useEffect(() => {
if (uid) {
const userRef = doc(db, "users", uid)
getDoc(userRef)
.then((docSnapshot) => {
const data = docSnapshot.data()
setCurrentUser(data)
})
}
}, [uid])

Refactor a functional component with React hooks

I have several functional components which share the same logic. So I would like to refactor them using React hooks. All of them make some calls to the server on mount to check if the order has been paid. If yes, paid state is set to true , and a file is being downloaded. On submit I check if paid state is set to true, if yes, the same file is being downloaded, if not, a new order is created and a user is being redirected to a page with a payment form.
I have already extracted all functions (getOrder(), getPaymentState(), createOrder(), initPayment() and downloadFile()) which make API calls to the server. How can I further optimize this code, so that I could move checkOrder(), checkPayment(), downloadPDF() and newOrder() outside the component to use the same logic with other components as well?
Here is my component:
const Form = () => {
const [paid, setPaid] = useState(false);
const [submitting, setSubmitting] = useState(false);
const [loading, setLoading] = useState(false);
const [data, setData] = useState({});
const checkOrder = async () => {
let search = new URLSearchParams(window.location.search);
let success = search.get("Success");
if (success) {
try {
const data = await getOrder();
setData(data);
checkPayment(data);
} catch (err) {
alert(err.message)
}
}
};
const checkPayment = async values => {
try {
const paid = await getPaymentState();
setPaid(paid);
downloadPDF(values);
} catch (err) {
alert(err.message)
}
};
const downloadPDF = async values => {
setLoading(true);
let downloadData = {
email: values.email,
phone: values.phone
}
const response = await downloadFile(downloadData, sendURL);
setLoading(false);
window.location.assign(response.pdf);
}
const newOrder = async values => {
setSubmitting(true);
const order = await createOrder(values, description, sum);
const paymentUrl = await initPayment(order, description, sum, returnURL);
setSubmitting(false);
window.location.assign(paymentUrl);
}
const onSubmit = async values => {
if (paid) {
try {
downloadPDF(data);
} catch (err) {
console.log(err);
}
} else {
try {
newOrder(values)
} catch (err) {
alert(err.message)
}
}
};
useEffect(() => {
checkOrder();
}, []);
return (
)
}
EDIT 1: I also need to be able to pass some data to this hook: downloadData, sendURL, description, sum and returnURL, which will be different in each case. downloadData then needs to be populated with some data from the values.
I would appreciate if you could point me at the right direction. I'm just learning React and I would really like to find the correct way to do this.
EDIT 2: I've posted my own answer with the working code based on the previous answers. It's not final, because I still need to move downloadPDF() outside the component and pass downloadData to it, but when I do so, I get an error, that values are undefined. If anybody can help me with that, I will accept it as an answer.
I made a quick refactor of the code and put it in a custom hook, it looks like search param is the key for when the effect needs to run.
const useCheckPayment = (search) => {
const [paid, setPaid] = useState(false);
const [submitting, setSubmitting] = useState(false);
const [loading, setLoading] = useState(false);
const [data, setData] = useState({});
const checkOrder = useCallback(async () => {
let paramSearch = new URLSearchParams(search);
let success = paramSearch.get('Success');
if (success) {
try {
//why not just pass it, makes getOrder a little less impure
const data = await getOrder(paramSearch);
setData(data);
checkPayment(data);
} catch (err) {
alert(err.message);
}
}
}, [checkPayment, search]);
const checkPayment = useCallback(async (values) => {
try {
const paid = await getPaymentState();
setPaid(paid);
downloadPDF(values);
} catch (err) {
alert(err.message);
}
}, []);
const downloadPDF = async (values) => {
setLoading(true);
const response = await downloadFile();
setLoading(false);
window.location.assign(response.pdf);
};
const newOrder = async (values) => {
setSubmitting(true);
const order = await createOrder();
const paymentUrl = await initPayment(order);
setSubmitting(false);
window.location.assign(paymentUrl);
};
const onSubmit = useCallback(
async (values) => {
if (paid) {
try {
downloadPDF(data);
} catch (err) {
console.log(err);
}
} else {
try {
newOrder(values);
} catch (err) {
alert(err.message);
}
}
},
[data, paid]
);
useEffect(() => {
checkOrder();
}, [checkOrder]); //checkOrder will change when search changes and effect is called again
return { onSubmit, submitting, loading };
};
const Form = () => {
const { onSubmit, submitting, loading } = useCheckPayment(
window.location.search
);
return '';
};
You can extract out all the generic things from within the Form component into a custom Hook and return the required values from this hook
The values which are dependencies and will vary according to the component this is being called from can be passed as arguments to the hook. Also the hook can return a onSubmit function to which you can pass on the downloadData
const useOrderHook = ({returnURL, sendURL, }) => {
const [paid, setPaid] = useState(false);
const [submitting, setSubmitting] = useState(false);
const [loading, setLoading] = useState(false);
const [data, setData] = useState({});
const checkOrder = async () => {
let search = new URLSearchParams(window.location.search);
let success = search.get("Success");
if (success) {
try {
const data = await getOrder();
setData(data);
checkPayment(data);
} catch (err) {
alert(err.message)
}
}
};
const checkPayment = async values => {
try {
const paid = await getPaymentState();
setPaid(paid);
downloadPDF(values);
} catch (err) {
alert(err.message)
}
};
const downloadPDF = async values => {
setLoading(true);
let downloadData = {
email: values.email,
phone: values.phone
}
const response = await downloadFile(downloadData, sendURL);
setLoading(false);
window.location.assign(response.pdf);
}
const newOrder = async (values, description, sum) => {
setSubmitting(true);
const order = await createOrder(values, description, sum);
const paymentUrl = await initPayment(order, description, sum, returnURL);
setSubmitting(false);
window.location.assign(paymentUrl);
}
const onSubmit = async ({values, downloadData: data, description, sum}) => {
if (paid) {
try {
downloadPDF(data);
} catch (err) {
console.log(err);
}
} else {
try {
newOrder(values, description, sum)
} catch (err) {
alert(err.message)
}
}
};
useEffect(() => {
checkOrder();
}, []);
return {onSubmit, loading, submitting, paid, data };
}
Now you can use this hook in component like Form as follows
const Form = () => {
const {onSubmit, newOrder, loading, submitting, paid, data } = useOrderHook({returnUrl: 'someUrl', sendURL: 'Some send URL'})
const handleSubmit = (values) => {
// since this function is called, you can get the values from its closure.
const data = {email: values.email, phone: values.phone}
onSubmit({ data, values, description, sum})// pass in the required values for onSubmit here. you can do the same when you actually call newOrder from somewhere
}
// this is how you pass on handleSubmit to React-final-form
return <Form
onSubmit={handleSubmit }
render={({ handleSubmit }) => {
return <form onSubmit={handleSubmit}>...fields go here...</form>
}}
/>
}
Based on the answers above I came up with the following code.
The hook:
const useCheckPayment = ({initialValues, sendUrl, successUrl, description, sum, downloadPDF}) => {
const [paid, setPaid] = useState(false);
const [loading, setLoading] = useState(false);
const [submitting, setSubmitting] = useState(false);
const [data, setData] = useState(initialValues);
const checkOrder = useCallback(
async () => {
let search = new URLSearchParams(window.location.search);
let success = search.get('Success');
if (success) {
try {
const data = await getOrder(search);
setData(data);
checkPayment(search);
} catch (err) {
alert(err.message);
}
}
}, [checkPayment]
);
const checkPayment = useCallback(
async (search) => {
try {
const paid = await getPaymentState(search);
setPaid(paid);
document.getElementById('myForm').dispatchEvent(new Event('submit', { cancelable: true }))
} catch (err) {
alert(err.message);
}
}, []
);
const newOrder = useCallback(
async (values) => {
setSubmitting(true);
const order = await createOrder(values, description, sum);
const paymentUrl = await initPayment(order, description, sum, successUrl);
setSubmitting(false);
window.location.assign(paymentUrl);
}, [description, sum, successUrl]
);
const downloadPDF = async (values, downloadData) => {
setLoading(true);
const response = await downloadFile(downloadData, sendUrl);
setLoading(false);
window.location.assign(response.pdf);
};
const onSubmit = useCallback(
async ({ values, downloadData }) => {
if (paid) {
try {
downloadPDF(values, downloadData);
} catch (err) {
console.log(err);
}
} else {
try {
newOrder(values);
} catch (err) {
alert(err.message);
}
}
},
[paid, downloadPDF, newOrder]
);
useEffect(() => {
checkOrder();
}, [checkOrder]);
return { onSubmit, submitting };
};
The component:
const sendUrl = 'https://app.example.com/send'
const successUrl = 'https://example.com/success'
const description = 'Download PDF file'
const sum = '100'
const Form = () => {
const handleSubmit = (values) => {
const downloadData = {
email: values.email,
phone: values.phone
}
onSubmit({ downloadData, values })
}
const { onSubmit, submitting } = useCheckPayment(
{sendUrl, successUrl, description, sum}
);
return (
<Form
onSubmit={handleSubmit}
render={({ handleSubmit }) => (
<form onSubmit={handleSubmit}></form>
)}
/>
)
}

Resources