What is the version 9 equivalent of this in firebase? - javascript

Im trying to convert my version 8 working code to version 9 but I cant figure out why im getting the error: 'FirebaseError: Expected type 'Query', but it was: a custom DocumentReference object'. It seems not like messagesRes but I put in a query and I think im correctly getting the subcollection.
version 8: working
export async function getServerSideProps(context) {
const chatRef = db.collection('chats').doc(context.query.id);
const messagesRes = await chatRef
.collection('messages')
.orderBy('timestamp', 'asc')
.get();
const messages = messagesRes.docs
.map(doc => ({
id: doc.id,
...doc.data(),
}))
.map(messages => ({
...messages,
timestamp: messages.timestamp.toDate().getTime(),
}));
const chatRes = await chatRef.get();
const chat = {
id: chatRes.id,
...chatRes.data(),
};
return {
props: {
messages: JSON.stringify(messages),
chat,
},
};
}
version 9:
export async function getServerSideProps(context) {
const chatRef = doc(db, 'chats', context.query.id);
const messagesRes = await getDocs(
query(collection(chatRef, 'messages'), orderBy('timestamp', 'asc'))
);
const messages = messagesRes.docs
.map(doc => ({
id: doc.id,
...doc.data(),
}))
.map(messages => ({
...messages,
timestamp: messages.timestamp.toDate().getTime(),
}));
const chatRes = await getDocs(chatRef);
const chat = {
id: chatRes.id,
...chatRes.data(),
};
return {
props: {
messages: JSON.stringify(messages),
chat,
},
};
}

According to the API documentation:
export declare function getDocs<T>(query: Query<T>): Promise<QuerySnapshot<T>>;
And in your code you are doing:
const chatRes = await getDocs(chatRef);
That is why you are seeing the error:
'FirebaseError: Expected type 'Query', but it was: a custom
DocumentReference object'.
because you are passing in your document reference and not a query.
Like you did here:
const messagesRes = await getDocs(
query(collection(chatRef, 'messages'), orderBy('timestamp', 'asc'))
);
You can see the correct usage of the API here.

Didn't work for me but I modified it a bit and used the firebase 9 collections and filtered the collection where the context.query.id is valid to SSR before the page load.
export async function getServerSideProps(context) {
const chatID = context.query.id;
const chatIDRef = collection(db, "chats");
//prep messages
const messagesRes = await getDocs(chatIDRef, where(chatID, "==", chatID));
console.log("ctx log: ", messagesRes);
const messages = messagesRes.docs
.map((doc) => ({
id: doc.id,
...doc.data(),
}))
.map((messages) => ({
...messages,
timestamp: messages.timestamp?.toDate().getTime(),
}));
//prep chat
const chatIDRef2 = doc(db, "chats", chatID);
const chatRef = await getDoc(chatIDRef2);
const chat = {
id: chatRef.id,
...chatRef.data(),
};
return {
props: {
messages: JSON.stringify(messages),
chat: chat,
},
};
}

Related

How to implement server side search filter in redux tool kit using query builder RTK?

I want to apply server side search filter by text using redux toolkit.
I have two query builder methods in place. One for fetching all items and second for fetching only filtered data.
Query builder for fetching all items is
getAllBlogs: builder.query<BlogType[], void>({
queryFn: async () => {
const collectionRef = collection(Firestore, BLOG_COLLECTION)
const q = query(collectionRef, limit(1000))
const resp = await getDocs(q)
return {
data: resp.docs.map((doc) => doc.data() as BlogType),
}
},
providesTags: (result) => {
const tags: { type: 'Blogs'; id: string }[] = [
{ type: 'Blogs', id: 'LIST' },
]
if (result) {
result.forEach(({ id }) => {
tags.push({
type: 'Blogs',
id,
})
})
}
return tags
},
}),
This works fine and I'm getting the whole list through useGetAllBlogsQuery data.
Query builder for fetching filtered data is here: (Partially completed)
getBlogsByTitle: builder.query<BlogType[], string>({
queryFn: async (title) => {
const collectionRef = collection(Firestore, BLOG_COLLECTION)
const q = query(
collectionRef,
where('searchIndex', 'array-contains', title),
limit(1000),
)
const resp = await getDocs(q)
return {
data: resp.docs.map((doc) => doc.data() as BlogType), // Correct data
}
},
// I'm trying to only push the resultant items in state. This is not working
providesTags: (result) => {
const tags: { type: 'Blogs'; id: string }[] = []
if (result) {
result.forEach(({ id }) => {
tags.push({
type: 'Blogs',
id,
})
})
}
return tags
},
}),
I have react component looks like this where I'm calling these queries.
const Blogs: NextPage = () => {
const { data: blogs } = blogsApi.useGetAllBlogsQuery()
const [getBlogsByTitle] = blogsApi.useLazyGetBlogsByTitleQuery()
const debounced = useDebouncedCallback(async (value) => {
const { data } = await getBlogsByTitle(value)
console.log(data) // Correct data
}, 500)
return (
<div>
<InputText
onChange={(e) => debounced(e.target.value)}
/>
</div>
)}
The above code has two functionalities.
Fetch all the items on initial load.
Filter when debounced function is being called.
What I want is when getBlogsByTitle is called it will auto update the same state blogs in redux and we don't have to do much.
We are getting correct response in getBlogsByTitle but this query is not updating state with only its filtered response.
I'm new to redux-toolkit. Can someone help me out here where am I doing wrong ?

Firebase Firestore nested collection in Cloud Functions

I am trying to implement the nested query with Firestore in Cloud Functions but stumbled upon issues with reading values in a for loop. Are there ways to adjust the following code so I could do some operations after reading all records from a collection?
const firestore = admin.firestore();
const today = new Date();
const snap = await firestore
.collection('places')
.where('endDate', '<', today)
.get()
const userIds = [...new Set(snap.docs.map((doc: any) => doc.data().owner))];
const updatePromises = snap.docs.map((d: any) => {
return d.ref.update({
isPaid: false,
isActive: false
})
})
await Promise.all(updatePromises);
const userCol = firestore.collection('users');
const userDocs = await Promise.all(userIds.map(uid => userCol.doc(uid).get()));
const userData = userDocs.reduce((acc, doc) => ({
...acc,
[doc.id]: doc.data()
}), {})
snap.docs.forEach((l: any) => {
const ownerData = userData[l.owner];
const { email, displayName } = ownerData;
console.log(email, displayName);
const message = {
// Some values
}
return sendGrid.send(message);
})
return null;
{ owner: '<firebaseUid'>, address: 'Royal Cr. Road 234' }
{ email: 'asdfa#afsdf.com' }
<firebase_uid>: {
displayName: '',
email: '',
phoneNumber: ''
}
The userIds.push(owner); will keep adding duplicate values in that array and if a single user is owner of multiple locations, you'll end up querying same data multiple times. If you are trying to read owner's data along with a location, then try refactoring the code as shown below:
const firestore = admin.firestore();
const today = new Date();
const snap = await firestore
.collection('locations')
.where('isActive', '==', true)
.get()
const userIds = [...new Set(snap.docs.map(doc => doc.data().owner))];
const updatePromises = snap.docs.map((d) => {
return d.ref.update({
isPaid: false,
isActive: false
})
})
// update documents
await Promise.all(updatePromises);
const userCol = firestore.collection("users")
const userDocs = await Promise.all(userIds.map(uid => userCol.doc(uid).get()))
const userData = userDocs.reduce((acc, doc) => ({
...acc,
[doc.id]: doc.data()
}), {})
// To get data of a location's owner
// console.log(userData[ownerId])
snap.docs.forEach((l) => {
const ownerData = userData[l.owner]
// run more logic for each user
})
return null;

Not getting any data from API

import axios from 'axios';
const url = 'https://covid19.mathdro.id/api';
export const fetchData = async () => {
try {
const { data: { confirmed, recovered, deaths, lastUpdate} } = await axios.get(url);
return {confirmed, recovered, deaths, lastUpdate};
} catch (error) {
}
}
export const fetchDailyData = async()=> {
try{
const data = await axios.get('${url}/daily');
console.log("DATA",data);
const modifiedData = data.map((dailyData) => ({
confirmed: dailyData.confirmed.total,
deaths: dailyData.deaths.total,
date: dailyData.reportDate,
}));
return modifiedData;
} catch(error){
console.log("DATA NOT FOUND");
var r = []
return r
}
}
Here I'mt trying to get data from this API: https://covid19.mathdro.id/api/daily
But Whenever I'm trying to call fetchDailyData , I'm only getting "DATA NOT FOUND" on the console
You used ' instead of ` (near below escape button) to use string templates:
const data = await axios.get(`${url}/daily`);
Besides the ' wrong syntax, the data.map will also throw an error: map is not a function, because you are trying to map the response and not the response.data.
You should do something like that:
const data = await axios.get('${url}/daily');
To:
const response = await axios.get(`${url}/daily`);
And your map:
const modifiedData = data.map((dailyData) => ({
To:
const modifiedData = response.data.map((dailyData) => ({

Get all records from collection with all refrences in Firestore

Hi I'm currently blocked because I can't get all records from a collection with references values.
I would like to get all records from collection events (it works) but when I wanna merge the category information associated with categoryId my code doesn't work anymore.
Events collection
Categories collection
export const getEventsRequest = async () => {
const output = [];
const data = await firebase.firestore().collection('events').get();
data.forEach(async (doc) => {
const {
name,
address,
city,
duration,
level,
startDate,
maxPeople,
categoryId,
} = doc.data();
const { name: categoryName, color } = (
await firebase.firestore().collection('categories').doc(categoryId).get()
).data();
output.push({
name,
address,
city,
duration,
level,
startDate,
maxPeople,
category: { name: categoryName, color },
});
});
return output;
};
Example testing in a React Native project
const [events, setEvents] = useState([]);
const [isEventsLoading, setIsEventsLoading] = useState(false);
const getEvents = async () => {
setEvents([]);
setIsEventsLoading(true);
try {
const evts = await getEventsRequest();
setEvents(evts);
setIsEventsLoading(false);
} catch (e) {
console.error(e);
}
};
useEffect(() => {
getEvents();
}, []);
console.log('events', events);
Output
events Array []
Expected
events Array [
{
name : "blabla",
address: "blabla",
city: "blabla",
duration: 60,
level: "hard",
startDate: "13/04/2021",
maxPeople: 7,
category: {
name: "Football",
color: "#fff"
},
},
// ...
]
I don't know if there is a simpler method to retrieve this kind of data (for example there is populate method on mongo DB).
Thank you in advance for your answers.
When you use CollectionReference#get, it returns a Promise containing a QuerySnapshot object. The forEach method on this class is not Promise/async-compatible which is why your code stops working as you expect.
What you can do, is use QuerySnapshot#docs to get an array of the documents in the collection, then create a Promise-returning function that processes each document and then use it with Promise.all to return the array of processed documents.
In it's simplest form, it would look like this:
async function getDocuments() {
const querySnapshot = await firebase.firestore()
.collection("someCollection")
.get();
const promiseArray = querySnapshot.docs
.map(async (doc) => {
/* do some async work */
return doc.data();
});
return Promise.all(promiseArray);
}
Applying it to your code gives:
export const getEventsRequest = async () => {
const querySnapshot = await firebase.firestore()
.collection('events')
.get();
const dataPromiseArray = querySnapshot.docs
.map(async (doc) => {
const {
name,
address,
city,
duration,
level,
startDate,
maxPeople,
categoryId,
} = doc.data();
const { name: categoryName, color } = (
await firebase.firestore().collection('categories').doc(categoryId).get()
).data();
return {
name,
address,
city,
duration,
level,
startDate,
maxPeople,
category: { name: categoryName, color },
};
});
// wait for each promise to complete, returning the output data array
return Promise.all(dataPromiseArray);
};

Firestore cloud function to recursively update subcollection/collectionGroup

I have this cloud function:
import pLimit from "p-limit";
const syncNotificationsAvatar = async (
userId: string,
change: Change<DocumentSnapshot>
) => {
if (!change.before.get("published") || !change.after.exists) {
return;
}
const before: Profile = change.before.data() as any;
const after: Profile = change.after.data() as any;
const keysToCompare: (keyof Profile)[] = ["avatar"];
if (
arraysEqual(
keysToCompare.map((k) => before[k]),
keysToCompare.map((k) => after[k])
)
) {
return;
}
const limit = pLimit(1000);
const input = [
limit(async () => {
const notifications = await admin
.firestore()
.collectionGroup("notifications")
.where("userId", "==", userId)
.limit(1000)
.get()
await Promise.all(
chunk(notifications.docs, 500).map(
async (docs: admin.firestore.QueryDocumentSnapshot[]) => {
const batch = admin.firestore().batch();
for (const doc of docs) {
batch.update(doc.ref, {
avatar: after.avatar
});
}
await batch.commit();
}
)
);
})
];
return await Promise.all(input);
};
How can I recursively update the notifications collection but first limit the query to 1.000 documents (until there are not more documents) and then batch.update them? I'm afraid this query will timeout since collection could grow big over time.
Posting a solution I worked out, not following the context of the question though but it can easily be combined. Hope it helps someone else.
import * as admin from "firebase-admin";
const onResults = async (
query: admin.firestore.Query,
action: (batch: number, docs: admin.firestore.QueryDocumentSnapshot[]) => Promise<void>
) => {
let batch = 0;
const recursion = async (start?: admin.firestore.DocumentSnapshot) => {
const { docs, empty } = await (start == null
? query.get()
: query.startAfter(start).get());
if (empty) {
return;
}
batch++;
await action(
batch,
docs.filter((d) => d.exists)
).catch((e) => console.error(e));
await recursion(docs[docs.length - 1]);
};
await recursion();
};
const getMessages = async () => {
const query = admin
.firestore()
.collection("messages")
.where("createdAt", ">", new Date("2020-05-04T00:00:00Z"))
.limit(200);
const messages: FirebaseFirestore.DocumentData[] = [];
await onResults(query, async (batch, docs) => {
console.log(`Getting Message: ${batch * 200}`);
docs.forEach((doc) => {
messages.push(doc.data());
});
});
return messages;
};

Categories