I am trying to get useremail from localstorage, but the email is not being updated instantly.
Solutions i have tried -
1. Using email in dependency array, this updates the email but people is not re-rendered in DOM
2. Using both email and people in dependency array, which is causing infinite calling of useEffect.
3.I have tried it without promise, directly in sequential flow which is also not updating email.
Please suggest the correct way of handling this.
const [people,setPeople]=useState([])
const [email,setEmail] = useState('')
useEffect(()=>{
new Promise((res,rej)=>{
setEmail(localStorage.getItem('userid')) //here is issue
if(email) res();
else rej(email);
}).then(
fire.firestore()
.collection('Users').where('Email','==',email)
.get().then((snapshot)=>{
console.log(snapshot)
setPeople(snapshot.docs[0].data().Name)
})
.catch(e=>{console.log(e)})
)
.catch((e)=>{console.log(e)})
},[email,people])
You can separate function getting email and people. I assume you want to get people only when having an email.
Getting email after component initially rendered
Then create useEffect for fire.firestore()... dependencies is email
const [people,setPeople]=useState([])
const [email,setEmail] = useState('')
useEffect(()=>{
setEmail(localStorage.getItem('userid'))
},[])
useEffect(()=>{
if(email){
fire.firestore()
.collection('Users').where('Email','==',email)
.get().then((snapshot)=>{
console.log(snapshot)
setPeople(snapshot.docs[0].data().Name)
})
.catch(e=>{console.log(e)})
}
},[email])
Wrapping the localStorage call in Promise makes it complicated. You need to make the firestore call when the email changes. Below are two solutions I can suggest.
The first one only runs when the component mounts and eliminates the email from state.
const [people, setPeople] = useState([]);
useEffect(() => {
// you might need to wrap this in a try catch as it may fail if a user has disabled access to localStorag
const email = localStorage.getItem("userid");
if (email) {
fire
.firestore()
.collection("Users")
.where("Email", "==", email)
.get()
.then((snapshot) => {
console.log(snapshot);
setPeople(snapshot.docs[0].data().Name);
})
.catch((e) => {
console.log(e);
});
}
}, []);
If you want the fire store effect to run whenever the email changes.
You can seperate the two effects and make the firestore effect depend on email changes as shown below.
const [people, setPeople] = useState([]);
const [email, setEmail] = useState('');
useEffect(() => {
const localStorageEmail = localStorage.getItem("userid");
if (email) {
setEmail(localStorageEmail);
}
}); // dependence array ignored will run everytime the component rerenders
useEffect(() => {
if (!email) return;
fire
.firestore()
.collection("Users")
.where("Email", "==", email)
.get()
.then((snapshot) => {
console.log(snapshot);
setPeople(snapshot.docs[0].data().Name);
})
.catch((e) => {
console.log(e);
});
}, [email]);
Related
I'm just trying to make a request to fetch only the threads that contain the current user's id.
If I remove my 'where' query, I can fetch all threads.
There is my code :
useEffect(() => {
const unsubscribe = firestore()
.collection('THREADS')
// query is empty
.where('usersIds', 'array-contains', ['60ddd70c7a3a1e8e62d14dac'])
.orderBy('latestMessage.createdAt', 'desc')
.onSnapshot(querySnapshot => {
const threadsQueried = querySnapshot
? querySnapshot.docs.map(documentSnapshot => {
return {
...documentSnapshot.data(),
};
})
: null;
setThreads(threadsQueried);
if (loading) {
setLoading(false);
}
});
return () => unsubscribe();
});
I already tried without putting my id into an array, but the component keeps refreshing, like that:
.where('usersIds', 'array-contains', '60ddd70c7a3a1e8e62d14dac')
My firebase datas:
I already check here https://stackoverflow.com/a/59053018/9300663
and here https://stackoverflow.com/a/59215461/9300663
Edit: So it is working when id is without brackets ('60ddd70c7a3a1e8e62d14dac') into the query
But my component keeps refreshing.
If I add an empty array or an array with dependencies to my useEffect, the query does not works anymore.
Edit 2: Query is working but get called two times and the second time get back with 'null', which is emptying my state.
So I found the solution when I tried another way to get my firebase query by using get() instead of onSnapshot():
firestore()
.collection('THREADS')
.where('usersIds', 'array-contains', user.id)
.orderBy('latestMessage.createdAt', 'desc')
.get()
.then(querySnapshot => {
const threadsQueried = querySnapshot.docs.map(documentSnapshot => {
return {
...documentSnapshot.data(),
};
});
setThreads(threadsQueried);
The problem with 'get()' was that the query only worked once and didn't update if new threads were created.
But it allowed me to have a firebase error asking me to create the indexes: 'usersIds' and 'latestMessage.createdAt'. After creating them, I was able to reuse my old code and everything's working correctly now.
useEffect(() => {
const unsubscribe = firestore()
.collection('THREADS')
.where('usersIds', 'array-contains', user.id)
.orderBy('latestMessage.createdAt', 'desc')
.onSnapshot(querySnapshot => {
const threadsQueried = querySnapshot.docs.map(documentSnapshot => {
return {
...documentSnapshot.data(),
};
});
setThreads(threadsQueried);
});
return () => unsubscribe();
}, []);
My code is supposed to load data on the first render so I fetched the documents from firebase in useEffect and stored them in the state "data". It works fine, all the documents are displayed on the page as I wanted. Now I wanted to delete individual documents on clicking a button under each of them. I did so with the help of a deleteDoc function which deletes the document from the database (and a file from storage if it exists). The deletion works fine but after the delete is successful, my code reloads the data from the database again for some reason causing the app to rerender the page again. I console logged at 2 different places in useEffect and found that useEffect doesn't execute the whole code inside it but only the part where it fetches the data from firebase. I just want it to perform that operation in the background and not interfere with the data state again. So how can I stop this re fetching every time I delete a document?
const [data, setData] = useState([]);
useEffect(() => {
if (user) {
user.getIdTokenResult().then((idTokenResult) => {
setAdmin(idTokenResult.claims.admin);
});
db.collection("files")
.orderBy("timeStamp", "desc")
.limit(5)
.onSnapshot((snapshot) => {
console.log(data, "In query"); // This executes after the delete operation
setData(
snapshot.docs.map((doc) => {
return [doc.data(), { doc_id: doc.ref.id }];
})
);
});
}
console.log(data, "Outside query"); // This doesn't execute after the delete operation
}, []);
Delete function:
const deleteDoc = async (e, item) => {
e.preventDefault();
// Deleting file from storage if it exists
if (item[0].file_name) {
const deleteTask = storage
.ref("users/" + user.uid)
.child(item[0].file_name);
deleteTask
.delete()
.then(() => {
console.log("File deleted Successfully");
})
.catch((err) => console.log(err));
}
// Deleting document from firestore collection.
db.collection("files")
.doc(item[1].doc_id)
.delete()
.then(() => {
setData(
data.filter((doc) => {
return doc[1].doc_id !== item[1].doc_id;
})
);
console.log("Document successfully deleted!", data);
})
.catch((error) => {
console.error("Error removing document: ", error);
});
};
By calling onSnapshot to get the data from Firestore, you're attaching a permanent listener that listens for realtime updates. So it is indeed expected that your callback gets called again (and thus the state updated again) when the database changes.
If you only want to get the data once, use get() instead of onSnapshot():
db.collection("files")
.orderBy("timeStamp", "desc")
.limit(5)
.get().then((snapshot) => {
...
According this document we can listen to changes in collection using onSnapshot So when you delete document in db, onSnapshot will listen to new change and will execute logic inside that function.
Solution to this is to Detach a listener
const unsubscribe = db.collection("files")
.orderBy("timeStamp", "desc")
.limit(5)
.onSnapshot(() => {
// Respond to data
// ...
});
// Later ...
// Stop listening to changes
unsubscribe();
Second solution is to use another state let's say fetchData and once you receive data on load set it to false like below:-
const [data, setData] = useState([]);
const [fetchData, setFetchData] = useState(true);
useEffect(() => {
if (user) {
user.getIdTokenResult().then((idTokenResult) => {
setAdmin(idTokenResult.claims.admin);
});
db.collection("files")
.orderBy("timeStamp", "desc")
.limit(5)
.onSnapshot((snapshot) => {
if(fetchData) {
setData(
snapshot.docs.map((doc) => {
return [doc.data(), { doc_id: doc.ref.id }];
})
);
setFetchData(false);
}
});
}
}, []);
I created a react native app and have to refresh my screen every time in order to get the newly added data from firebase. I'm new to firebase and I thought I can use snapshot to get the current data but I still have to refresh my app every time a new event is created in order to see all the updated events on this view. Any help would be appreciated
export default function EventsHostedScreen() {
const navigation = useNavigation();
const [eventsData, setEventsData] = useState([]);
useEffect(() => {
async function fetchData() {
const currentUser = await firebase.auth().currentUser.uid;
result = [];
const eventsCollection = firebase.firestore().collection('events');
eventsCollection.get().then((snapshot) => {
snapshot.docs.forEach((doc) => {
if (doc.exists === true && doc.data().userId !== null) {
if (doc.data().userId === currentUser) {
result.push(doc.data());
}
}
});
setEventsData(result);
});
console.log('RESULT==>', result);
}
fetchData();
}, []);
You can listen to changes to a document or collection with the onSnapshot method. In addition to that, I would suggest a couple of changes to your code.
It seems to me like you want to query for documents where the userId is same as the current user's id. It would be easier to include this in the query with the where method. That way you won't have to filter the documents with if statements like you currently are. You will also save on Firestore reads, as right now you are getting all events, but with the where method you will only read the documents where the equality clause is true.
I would also include a check for whether you have the currentUser available, unless you are 100% sure this component won't ever be rendered while the currentUser is loading. And you don't need to await the currentUser and therefore don't need an async function anymore.
With these changes your useEffect could look something like the following.
useEffect(() => {
// Check if currentUser exists to avoid errors
if (!firebase.auth().currentUser) {
return;
}
const currentUser = firebase.auth().currentUser.uid;
// Create subscription to listen for changes
const unsubscribe = firebase
.firestore()
.collection('events')
.where('userId', '==', currentUser)
.onSnapshot((snapshot) => {
const result = [];
snapshot.forEach((doc) => {
result.push(doc.data());
});
setEventsData(result);
});
// Remove the listener when component unmounts
return () => unsubscribe();
// Add currentUser to useEffect dependency array, so useEffect runs when it changes
}, [firebase.auth().currentUser]);
I have a react component with this state
const [name, setName] = useState('')
const [comment, setComment] = useState('')
const [notes, setNotes] = useState([])
this function handles the input elements to fill the order
const handleComments = () => {
setNotes([...notes, {
name,
comment
}])
setName('')
setComment('')
}
and this function sends the info to the server
const update = async () => {
const newNotes = notes.map(note => ({
name,
comment
}))
return updateNotesPromise(newNotes)
}
here I have a button that has to execute both functions
<Button onClick={} />
How can I create a function that is passed through the onClick method and executes handleComments in order to load the info on the DOM and then, once that info there, executes the update function and saves the order info into the DB ?
It looks like you're using functional components, so you can create a useEffect that makes an API put request whenever notes gets updated:
useEffect(()=> {
updateNotesPromise(notes);
},[notes])
I'm assuming updateNotesPromise is a function that makes your request call? It's also unclear why newNotes is being mapped from notes, or why update is async when it doesn't await anything. Your onClick would simply trigger handleNotes (I'm assuming that is your submit button).
Here's a way to handle the component updating and server communicating with error handling:
const onButtonClicked = useCallback(async (name, comment) => {
// cache the olds notes
const oldNotes = [...notes];
// the updated notes
const newNotes = [...notes, {
name,
comment
}];
// update the component and assume the DB save is successful
setNotes(newNotes);
try {
// update the data to DB
await updateNotesPromise(newNotes);
} catch(ex) {
// when something went wrong, roll back the notes to the previous state
setNotes(oldNotes);
}
}, [notes]);
I try to put a listener on Firebase that will replicate a value in the matching element in Firestore.
exports.synchronizeDelegates = functions.database.ref(`delegates/{userId}/activities`).onUpdate((event) => {
const userKey = event.data.ref.parent.key
console.log("User Key:" + userKey)
return admin.database().ref(`delegates/${userKey}/email`).once('value', snapshot => {
let email = snapshot.val()
console.log("Exported Email:" + email)
const userRef = admin.firestore().collection('users')
const firestoreRef = userRef.where('email', "==", email)
firestoreRef.onSnapshot().update({ activities: event.data.toJSON() })
}).then(email => {
console.log("Firebase Data successfully updated")
}).catch(err => console.log(err))
}
)
This function is able to retrieve and locate the elemnt needed to target the right document in firestore, but the .update()function still error firestoreRef.update is not a function
I try several ways to query but I still have this error.
How to properly query then update a document in this scenario?
The onSnapshot() method of Query introduces a persistent listener that gets triggered every time there's a new QuerySnapshot available. It keeps doing this until the listener is unsubscribed. This behavior is definitely not what you want. Also, there's no update() method on QuerySnapshot that your code is trying to call.
Instead, it looks like you want to use get() to fetch a list of documents that match your query, then update them all:
exports.synchronizeDelegates = functions.database.ref(`delegates/{userId}/activities`).onUpdate((event) => {
const userId = event.params.userId
console.log("User Key:" + userKey)
return admin.database().ref(`delegates/${userId}/email`).once('value', snapshot => {
let email = snapshot.val()
console.log("Exported Email:" + email)
const usersRef = admin.firestore().collection('users')
const query = usersRef.where('email', "==", email)
const promises = []
query.get().then(snapshots => {
snapshots.forEach(snapshot => {
promises.push(snapshot.ref.update(event.data.val()))
})
return Promise.all(promises)
})
}).then(email => {
console.log("Firebase Data successfully updated")
}).catch(err => console.log(err))
}
Note that I rewrote some other things in your function that were not optimal.
In general, it's a good idea to stay familiar with the Cloud Firestore API docs to know what you can do.