I'm receiving a callback from axios after a post request. The callback contains data that I need to use. I want add the callback json data my existing state - but after googling I learnt that callbacks + usestate hook doesn't work together well.
const [party, setParty] = useState("")
const SubmitParty = (e) => {
e.preventDefault()
const PartyData = {
party: party,
firstname: firstname,
lastname: lastname,
email: email,
number: number
}
axios.post(
'/api/new/party',
{
party: party,
firstname: firstname,
lastname: lastname,
email: email,
number: number
},
{
params: {
"secret_token": logged.token
}
}
).then(res => {
var user = res.data.data;
console.log(user); //I can see the response here and the array I want.
setParty(user);
console.log(party); // returns just a blank line. Expected to be same as user
}).catch(err => {
console.log(err)
alert("Oops something went wrong. If this problem continues, please contact support asap!")
});
}
I tried using a useeffect afterwards, but it's a catch 22, cause I need the state to deal with with in in useeffect accurately.
If you use useEffect like this your component will send the API request and update party when it resolves.
const [party, setParty] = useState("");
useEffect(e => {
e.preventDefault();
const PartyData = {
party: party,
firstname: firstname,
lastname: lastname,
email: email,
number: number
};
axios
.post(
"/api/new/party",
{
party: party,
firstname: firstname,
lastname: lastname,
email: email,
number: number
},
{
params: {
secret_token: logged.token
}
}
)
.then(res => {
var user = res.data.data;
console.log(user); //I can see the response here and the array I want.
setParty(user);
console.log(party); // returns just a blank line. Expected to be same as user
})
.catch(err => {
console.log(err);
alert(
"Oops something went wrong. If this problem continues, please contact support asap!"
);
});
});
By default this will run on every render. If you want the effect to run only once then you should set useEffect's 2nd parameter to be an empty array, or if you only want it to run when a specific few items change then you can put those in the array.
Related
I am creating a user Context to keep track of my app's user data like name, email, phone etc. Where & how should I be updating this user context? Currently I am updating the context in the same component functions that are calling the database to update the user record. Is this a correct pattern? Or should the context be automatically updated by the database record changes somehow? Separately on authentication, I am reading the user record from the database and setting it on the user context. Is it correct to set the user on auth or should it be on every load? What is the life cycle of the context? Under which conditions should I update the user context? Is updating the context always manual or is there some method to keep it in sync with the database changes?
I've done lots of reading and asking around and can't seem to figure this out. Thanks in advance!
Tech stack: Firebase Firestore, React Native, Expo.
index.tsx
function RootNavigator() {
const { authUser, setAuthUser, user, setUser } = useContext(AuthContext);
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
// onAuthStateChanged returns an unsubscriber
const unsubscribeAuth = auth.onAuthStateChanged(
async (authenticatedUser) => {
authenticatedUser ? setAuthUser(authenticatedUser) : setAuthUser(null);
setIsLoading(false);
}
);
// unsubscribe auth listener on unmount
return unsubscribeAuth;
}, [authUser]);
// calls the server to populate the user object & set it in context
useEffect(() => {
const loadUserFromFirestore = async () => {
const dbUser = await getUser(authUser?.uid);
if (dbUser !== null) {
// sets user context
setUser(dbUser);
} else {
}
};
loadUserFromFirestore();
}, [authUser]); // is this the correct condition?
EditProfileScreen.tsx
export default function EditProfileScreen({
navigation,
}: RootStackScreenProps<"EditProfile">) {
const { user, setUser } = useContext(AuthContext);
const saveProfile = () => {
// update user in database
updateUser({
id: authUser?.uid,
firstName: firstName,
lastName: lastName,
email: email,
} as User);
// updating user context
// context should only be updated from database state?
// how often does context get updated? is there a way to update this when we know db data changed?
setUser({
...user,
firstName: firstName,
lastName: lastName,
email: email,
});
navigation.goBack();
};
firebaseMethods.tsx
export async function updateUser(user: User) {
console.log("Updating user");
const userRef = doc(database, "users", user.id);
await updateDoc(userRef, {
firstName: user.firstName,
lastName: user.lastName,
email: user.email,
updatedAt: serverTimestamp(),
});
}
I have been using the following function to store a file in Firebase Storage, return the URL, which I then store along with some other fields, in Firestore.
Where do I add an 'await' or how do I add a promise so that the 'history.push("/") is not called until all operations have completed? At the moment I think it's pushing me on to the next page before it's finished.
async function handleSubmit(e) {
e.preventDefault()
const collectionRef = useFireStore.collection('users').doc(`${currentUser.uid}`);
const storageRef = useStorage.ref("avatars")
const fileRef = storageRef.child(`${uuidv4()}`)
fileRef.put(file).then(() => {
fileRef.getDownloadURL().then(function (url) {
collectionRef.set({
createdAt: timestamp(),
email: currentUser.email,
userid: currentUser.uid,
username: username,
firstname: firstname,
lastname: lastname,
avatar: url
})
});
})
history.push("/")
}
What would be some best-practices here, please?
Kind regards, Matt
You can do something like this,
async function handleSubmit(e) {
e.preventDefault()
const collectionRef = useFireStore.collection('users').doc(`${currentUser.uid}`);
const storageRef = useStorage.ref("avatars")
const fileRef = storageRef.child(`${uuidv4()}`)
await fileRef.put(file);
const url = await fileRef.getDownloadURL();
await collectionRef.set({
createdAt: timestamp(),
email: currentUser.email,
userid: currentUser.uid,
username: username,
firstname: firstname,
lastname: lastname,
avatar: url
});
history.push("/")
}
Refer: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function
Here is an example of my code.
let appointment
const [AddMemberTitle] = useMutation(ADD_MEMBER_TITLE_MUTATION)
const [CreateMember] = useMutation(CREATE_MEMBER_MUTATION, {
onCompleted: (newMemberData) => {
setMemberId(newMemberData.CreateMember.id)
//run some code using the Member id and the values from my Formik component and the appointment variable
},
})
const onSubmit = async (values, onSubmitProps) => {
appointment = values.appointment
CreateMember({
variables: {
firstName: values.firstName,
middleName: values.middleName,
lastName: values.lastName,
gender: values.gender,
phoneNumber: values.phoneNumber,
whatsappNumber: values.whatsappNumber,
email: values.email,
dob: values.dob,
maritalStatus: values.maritalStatus,
occupation: values.occupation,
},
})
}
return (
//Then my jsx goes here
)
The rest of the code is working as expected, I just need to run the AddMemberTitle mutation using information that is generated after the CreateMember mutation runs.
I tried creating a variable at the top level of the component, and I'm able to assign a value to it in onSubmit but I'm unable to use it in the onCompleted function of Create Member
One of (a few) solutions can be using a promise returned from the mutation calling function (CreateMember returned from useMutation hook).
You can use await or .then() syntax:
const onSubmit = async (values, onSubmitProps) => {
const {
appointment,
otherFormikValue,
...rest // assuming only CreateMember values and mutation names matching
} = values;
const { data: newMemberData } = await CreateMember({ variables: {...rest} });
const { data: someData } = await InsertAppointmentMember(
{ variables: {
memberId: newMemberData.CreateMember.id,
appointmentId: appointment.id
} });
I'm working on registration. everything was good before adding the profile image.
this is the registration page onSubmit method
const handleSubmit = (event) => {
event.preventDefault();
const user_data = {
userID: userID,
password: password,
firstName: firstName,
lastName: lastName,
email: email
}
dispatch(register(user_data, profileImageFile))
};
and this is the actual axios
const register = async (user_data, imgFile) => {
console.log(user_data, imgFile)
formData.append('userID', user_data.userID);
formData.append('password', user_data.password);
formData.append('firstName', user_data.firstName);
formData.append('lastName', user_data.lastName);
formData.append('email', user_data.email);
formData.append('imgFile', imgFile);
for (var pair of formData.entries()) { console.log(pair[0] + ', ' + pair[1]); }
return await axios.post("/registration", formData
, {
headers: { 'Content-Type': 'multipart/form-data' }
}
)
}
I verified formdata had all the text data and image well. but at the backend, any data wasn't appear.
When I put in another data without an image file, it worked well.
what is the problem with that?
I'm currently setting up Firebase for my React Native app. Authentication & basic writing/reading work fine.
Though, I struggle to write data to the database right after the initial authentication because my "signUp" and "writeUserData" functions execute at the same time (and at this moment there is no userId yet).
Here is my code:
...
module.exports = React.createClass({
getInitialState(){
return({
email: '',
password: '',
confirmPassword: '',
result: '',
})
},
signUp() {
if (this.state.password === this.state.confirmPassword) {
let {email, password} = this.state;
firebaseApp.auth().createUserWithEmailAndPassword(email, password)
.catch(error => this.setState({result: error.message
}));
this.writeUserData(email);
} else {
this.setState({result: 'Passwords do not match.'})
}
},
writeUserData(email) {
let userId = firebaseApp.auth().currentUser.uid;
firebase.database().ref('users/' + userId + '/').set({
info: {
firstName: 'Jonas',
email: email,
},
currentState: {
currentDay: 0,
currentSeries: 0,
}
});
},
render() {
...
I can't find any good documentation about this. Can someone give me a pointer?
Thanks!
The createUserWithEmailAndPassword() method returns Promise, you have to use it's .then for updating the user details, like this:
firebaseApp.auth()
.createUserWithEmailAndPassword(email, password)
.then( user => {
user.updateProfile({
displayName: 'Jonas'
});
user.updateEmail(email);
})
.catch(error => this.setState({result: error.message}));
Note also the appropriate way to update user' email
I found the solution within Stack Overflow. Much easier than expected: How do you include a username when storing email and password using Firebase (BaaS) in an Android app?
EDIT: I'm using now the onAuthStateChangedmethod instead.
componentDidMount() {
firebaseApp.auth().onAuthStateChanged(user => {
if(user) {
this.writeUserData(user.uid, user.email, this.state.firstName);
}
})
},
writeUserData(userId, email, firstName) {
firebase.database().ref('users/' + userId + '/').set({
info: {
firstName: firstName,
email: email,
},
currentState: {
currentDay: 0,
currentSeries: 0,
}
});
},