Firebase version 9.6.9
I'm using onSnapshot function to get the data from the firestore.
Code:
const [posts, setPosts] = useState([])
useEffect(() => {
return onSnapshot(
query(collection(db, 'posts'), orderBy('timestamp', 'desc')),
(snapshot) => {
console.log(snapshot.docs)
}
)
}, [db])
snapshot.docs logs a metadata and not the data I need (the posts collection).
The snapshot.docs is an array of QueryDocumentSnapshot. You can get data from your documents, by using .data() method on every snapshot as shown below:
return onSnapshot(
query(collection(db, 'posts'), orderBy('timestamp', 'desc')),
(snapshot) => {
const result = snapshot.docs.map((d) => ({
id: d.id,
...d.data()
}))
console.log('>>> Documents', result)
}
)
The metadata has some information about the snapshots such as the source.
Related
I'm creating an app in React Native that depends on realtime updates. When a user updates the data in their session, another user needs to immediately see that update without refreshing their app. I'm using RTK Query to manage my store, but can't figure out how to close the memory leak if I use onSnapShot in my query. Is there another Redux solution I need to consider?
I've tried passing the data through props to manage the data but the management becomes a little complicated when using complex components.
I start with the following in the component but want to move it to the api:
export const teamPlayersApi = createApi({
reducerPath: "teamPlayers",
baseQuery: fakeBaseQuery(),
tagTypes: ["TeamPlayer"],
endpoints: (builder) => ({
fetchTeamPlayersByMatchId: builder.query({
async queryFn(matchId) {
let players = [];
return new Promise((resolve, reject) => {
const playersRef = query(
collection(db, "teamPlayers"),
where("playerMatchId", "==", matchId)
);
//real time update
onSnapshot(playersRef, (snapshot) => {
players = snapshot.docs.map((doc) => ({
id: doc.id,
player: doc.data()
}));
resolve({ data: players });
});
});
}
})
})
})
This has two different parts:
a queryFn getting the initial data using onValue. This is the point where your query enters a loading state and finishes at some point with a first value.
a onCacheEntryAdded lifecycle function that calls onSnapshot, updates values and holds the subscription. This is the point where a subscription is generated and your data is updated with new values while your component uses the cache entry.
export const teamPlayersApi = createApi({
reducerPath: "teamPlayers",
baseQuery: fakeBaseQuery(),
tagTypes: ["TeamPlayer"],
endpoints: (builder) => ({
fetchTeamPlayersByMatchId: builder.query({
async queryFn(matchId) {
let players = [];
return {
data: await new Promise((resolve, reject) => {
const playersRef = query(
collection(db, "teamPlayers"),
where("playerMatchId", "==", matchId)
);
// probably more logic here to get your final shape
onValue(
playersRef,
(snapshot) => resolve(snapshot.toJSON()),
reject
);
}),
};
},
async onCacheEntryAdded(
matchId,
{ updateCachedData, cacheDataLoaded, cacheEntryRemoved }
) {
let unsubscribe = () => {};
try {
await cacheDataLoaded;
const playersRef = query(
collection(db, "teamPlayers"),
where("playerMatchId", "==", matchId)
);
unsubscribe = onSnapshot(playersRef, (snapshot) => {
players = snapshot.docs.map((doc) => ({
id: doc.id,
player: doc.data(),
}));
updateCachedData((draft) => {
// or whatever you want to do
draft.push(players);
});
});
} catch {}
await cacheEntryRemoved;
unsubscribe();
},
}),
}),
});
I'm using firestore to store posts each post could have simple properties such as {title: 'hi', comment: true} I'm able to easily fetch the user's specific posts since my collection structure looks like this: posts/user.id/post/post.name so an example will be posts/1234sofa/post/cool day
with this way of structuring, I'm able to easily fetch data for the user, but I'm having trouble with two things how do I fetch and display all posts for my main feed, and what's the most effective way of doing this? here is my current function for fetching user-specific data:
const submitpost = async () => {
try {
const collectionRef=collection(db,`posts`,user.uid.toString(),'post')
await addDoc(collectionRef, {
post: post,
timestamp: serverTimestamp(),
canComment: switchValue,
user: user.uid,
avatar: user.photoURL,
username: user.displayName,
});
toast({ title: "posted", status: "success", duration: 2000 });
} catch (error) {
console.log(error);
}
};
this specific function creates a structure like this in firebase posts are just takes and take is singular post respectively I just changed the name so its easier to understand:
now here is how im fetching the data for my spefic user:
const [user] = useAuthState(auth);
const [takes, settakes] = useState([]);
const getData = async () => {
// if user is present run function
if (user) {
// const docRef = doc(db, "users", user.uid);
// const collectionRef = collection(docRef, "takes");
// const querySnapshot = await getDocs(collectionRef);
try {
const docRef = doc(db, "posts", user.uid);
const collectionRef = collection(db,'posts',user.uid,'takes');
const querySnapshot = await getDocs(collectionRef);
const data = querySnapshot.docs.map((d) => ({
id: d.id,
...d.data(),
}));
settakes(data);
} catch (error) {
console.log(error);
}
//
}
};
here is the function that doesn't work when fetching all data for main feed:
const [user]=useAuthState(auth)
const [allfeed, setallfeed] = useState([])
const getData = async () => {
if(user){
const collectionRef = collection(db, "posts");
const querySnapshot = await getDocs(collectionRef);
const data = querySnapshot.docs.map((d) => ({
id: d.id,
...d.data(),
}));
// get data from firebase
setallfeed(data)
}
}
useEffect(() => {
getData()
console.log('ran');
console.log(allfeed);
// rerun when user is present
}, [user]);
when I console log the allfeed it returns an empty array so my main problem is how to do I get all the data from the posts collection meaning posts/userid/post/post.title I need to get these for every user. and secondly is there a more efficient way to structure my data?
I would suggest using the onSnapshot() method if you want realtime updates from a collection or a specific document.
setState() does not make changes directly to the state object. It just creates queues for React core to update the state object of a React component. If you add the state to the useEffect, it compares the two objects, and since they have a different reference, it once again fetches the items and sets the new items object to the state. The state updates then triggers a re-render in the component. And on, and on, and on...
If you just want to log your data into your console then you must use a temporary variable rather than using setState:
const getData = async () => {
if(user){
// Using `getDocs`
const collectionRef = collection(db, "posts");
const querySnapshot = await getDocs(collectionRef);
const data = querySnapshot.docs.map((d) => ({
id: d.id,
...d.data(),
}));
console.log(data)
// ============================================= //
// Using `onSnapshot()`
const q = query(collection(db, "posts"));
const unsubscribe = onSnapshot(q, (querySnapshot) => {
const data = querySnapshot.docs.map(d => ({
id: d.id,
...d.data()
}))
console.log(data)
});
}
}
useEffect(() => {
getData();
}, []);
You could also use multiple useEffect() to get the updated state of the object:
const getData = async () => {
if(user){
// Using `getDocs`
const collectionRef = collection(db, "posts");
const querySnapshot = await getDocs(collectionRef);
const data = querySnapshot.docs.map((d) => ({
id: d.id,
...d.data(),
}));
setallfeed(data)
// ============================================= //
// Using `onSnapshot()`
const q = query(collection(db, "posts"));
const unsubscribe = onSnapshot(q, (querySnapshot) => {
const data = querySnapshot.docs.map(d => ({
id: d.id,
...d.data()
}))
setallfeed(data)
});
}
}
useEffect(() => {
getData();
}, [])
useEffect(() => {
console.log(allfeed);
}, [allfeed]);
If you want to render it to the component then you should call the state in the component and map the data into it. Take a look at the sample code below:
const getData = async () => {
if(user){
// Using `getDocs`
const collectionRef = collection(db, "posts");
const querySnapshot = await getDocs(collectionRef);
const data = querySnapshot.docs.map((d) => ({
id: d.id,
...d.data(),
}));
setallfeed(data)
// ============================================= //
// Using `onSnapshot()`
const q = query(collection(db, "posts"));
const unsubscribe = onSnapshot(q, (querySnapshot) => {
const data = querySnapshot.docs.map(d => ({
id: d.id,
...d.data()
}))
setallfeed(data)
});
}
}
useEffect(() => {
getData()
}, []);
return (
<div>
<p>SomeData: <p/>
{items.map((item) => (
<p key={item.id}>{item.fieldname}</p>
))}
</div>
);
For more information you may checkout these documentations:
Get data with Cloud Firestore
Get realtime updates with Cloud Firestore
const { id } = useParams();
const [detailData, setDetailData] = useState({});
useEffect(() => {
const q = query(collection(db, "movie"));
onSnapshot(q, (querySnapshot) => {
querySnapshot.docs.map((doc) => {
if (doc.exists) {
setDetailData(doc.data());
} else {
console.log("no doc");
}
})
.catch((error) => {
console.log("Error:", error);`enter code here`
});
});
}, [id]);
how can i pass id of document in this code tried lots of tutorials and official documentation also .
QuerySnapshot.docs returns an array of QuerydocumentSnapshot which has an id field:
https://firebase.google.com/docs/reference/node/firebase.firestore.QueryDocumentSnapshot
This is located outside of the doc.data field. Doc.data contains the data in your DB object.
How to set a specific quarry for a sub collection when using a snapshot ?
I am tring to sort sub collection resutls by createdAt with the limit of last 5
useEffect(() => {
//const querydata = query(docRefComm, orderBy('createdAt'), limit(5));
let collectionRef = collection(db, 'Comm', docId, 'messages');
const unsub = onSnapshot(collectionRef , (doc) => {
doc.forEach((el) => {
console.log(el.data());
});
});
return () => {
unsub();
};
}
}, []);
You can build a query() using CollectionReference as shown below:
let collectionRef = collection(db, 'Comm', docId, 'messages');
const q = query(collectionRef, orderBy('createdAt'), limit(5))
const unsub = onSnapshot(q , (qSnap) => {
})
Checkout Listen to multiple documents in a collection in the documentation.
I have the following code that log to console all my document data. The problem is that I only receive updates as the function runs. I like to know how to setup a listener that log to console when data changes
const db = firebase.firestore();
const todoRef = db.collection("todo");
const list = (date) => {
console.log(date);
return todoRef
.where("owner", "==", firebase.auth().currentUser.uid)
.where("date", "==", date)
.get()
.then((querySnapshot) => {
const data = [];
querySnapshot.forEach((doc) => {
const d = {
id: doc.id,
...doc.data(),
};
data.push(d);
});
return data;
});
};
console.log("database stuff", list("2021 - 07 - 12"));
There's a listen to multiple documents section in the documentation.
db.collection("cities").where("state", "==", "CA")
.onSnapshot((querySnapshot) => {
var cities = [];
querySnapshot.forEach((doc) => {
cities.push(doc.data().name);
});
console.log("Current cities in CA: ", cities.join(", "));
});
You just need to use onSnapshot instead of get.