how to check if nested docs and collection in firebase exist and if don't how to create, in react native - reactjs

I am new to Firebase and I have to create a chat system. I found that the doc structure should be nested
e.g if a person sends a message, a new doc with its id will be created in the main collection and then a new collection will be added to the doc. now each doc in that nested collection will be considered as a message obj.
a rough sketch of how the new message in the nested document will be added
but the problem is when there is no doc with UI exist or no collection in that doc exist
firestore().collection("chatBox").doc(uid).collection("message").add(
{
text: "this is my first message",
user: {_id:356},
avatar: "link of avatar",
name: "john",
createdAt: new Date().getTime()
}
)
const sendMessage = async (messages = []) => {
const msg = messages[0];
const id = msg.user?._id?.toString();
const collectionRef = firestore().collection(CHATBOX);
const doc = collectionRef.doc(id);
const docExists = await doc.get().then(function (doc) {
return doc.exists;
});
if (docExists) {
const collection = doc.collection(MESSAGES);
const isCollectionEmpty = collection.get().then(col => {
return col.empty;
});
if (isCollectionEmpty) doc.set({id: MESSAGES});//creating new collection
else collection.add({...msg, createdAt: new Date().getTime()});//inserting doc if collection exist
} else {
collectionRef.add(id);// creating doc in main collection
}
};

The ability to create a document only if it does not exist can be done using the following Transaction. Here, the createDocIfNotExist method creates the document with the given data, only if it does not already exist. It returns a Promise<boolean> indicating whether the document was freshly created or not.
async createDocIfNotExist(docRef, initData) {
return docRef
.firestore
.runTransaction((transaction) => {
const docSnap = await transaction.get(docRef);
if (docSnap.exists)
return false; // exists already
transaction.set(docRef, initData);
return true; // was created
});
}
Applying this to your code then gives:
const sendMessage = async (messages = []) => {
const msg = messages[0];
const msgUserId = msg.user!._id!.toString(); // id renamed, consider using senderId/recipientId instead
const chatboxColRef = firestore().collection(CHATBOX); // collectionRef renamed
const userChatboxDocRef = chatboxColRef.doc(msgUserId); // doc renamed
const isNewChatbox = await createDocIfNotExist(
userChatboxDocRef,
{ id: msgUserId }
);
const userChatboxMessagesColRef = userChatboxDocRef.collection(MESSAGES); // collection renamed
return userChatboxMessagesColRef
.add({
...msg,
createdAt: new Date().getTime() // consider using firebase.firestore.FieldValue.serverTimestamp() instead
});
};
This can be further reduced to:
const sendMessage = async (messages = []) => {
const msg = messages[0];
const msgUserId = msg.user!._id!.toString();
const userChatboxDocRef = firestore()
.collection(CHATBOX);
.doc(msgUserId);
await createDocIfNotExist(
userChatboxDocRef,
{ id: msgUserId }
);
return userChatboxDocRef
.collection(MESSAGES)
.add({
...msg,
createdAt: new Date().getTime()
});
};
Note: Avoid using the variable name doc as it is ambiguous and could be an instance of DocumentData, DocumentReference, or DocumentSnapshot (at minimum, use docData, docRef and docSnap/docSnapshot respectively). Similarly, use colRef for a CollectionReference and qSnap/querySnap for QuerySnapshot objects.

Related

ReactJS/Firestore: how to add sub-collection to existing firestore document

I'm trying to create a new collection in an existing firestore document :
Here is my firestore :
I'd like to create the "general" collection automaticaly.
Here is my code :
function App() {
const dataGeneral = {
email: "",
firstname: "",
lastname: "",
phone: "",
gdpr: false,
};
useEffect(() => {
const migrateData = async () => {
const usersCollectionRef = collection(db, "users"); //gets the root collection
const { idUser } = await addDoc(usersCollectionRef, {}); // Creates a new document in the root collection
const usersSubCollectionGeneralRef = collection(db,`users/${idUser}/general`); //Creates a sub collection in the just created document
const { idGeneral } = await addDoc(usersSubCollectionGeneralRef, { dataGeneral }); //Creates a document in the newly created collection
};
migrateData();
}, []);
}
how to add sub-collection to existing firestore document
You don't add sub-collection explicitly, since Firestore cannot have empty collections. Instead sub-collections will be added automatically when adding a document to a collection. (And also deleted automatically if they don't hold any documents anymore)
That being said, your code destructures idUser and idGeneral from objects where those properties don't exist. You probably want to access the property id like this:
// Create a new, empty document in `users`
const usersRef = collection(db, 'users');
const user = awaitDoc(usersRef, {
data: 'new document in users'
});
// Create a new document in sub-collection `general`
const generalRef = collection(db, `users/${user.id}/general`);
const general = awaitDoc(generalRef, {
data: 'new document in sub-collection general'
});
Your intention might have been to rename the destructured property. If that's the case, see MDN for how to do that.
const {id: idUser } = await addDoc(...)
console.log('id of new document: ', idUser)

How to send an object inside an object in postman

const mongoose = require("mongoose")
const Schema = mongoose.Schema
const MonstruoSchema = Schema ({
name:String,
item:[{
arma:String
}]
})
module.exports = mongoose.model ("Monstruo",MonstruoSchema);
which is a "Monstruo" object that contains an array of items called "arma", my intention is to have a monster which can have a list of n weapons.
https://i.stack.imgur.com/tyWWH.png
I am trying to add a "arma" through postman as follows but I have not been able to get the item to add as intended
app.post("/api/monstruo",(req,res) => {
let monstruo = new Monstruo()
monstruo.name = req.body.name
monstruo.item.nombre= req.body.nombre
monstruo.save((err,monstruoGuardado)=>{
if (err) res.status(500).send("no se guardo bienn")
res.status(200).send({monstruo:monstruoGuardado})
})
})

React Firebase async return of IDs

I have a project named booking app for companies and I'm trying to add "services" in firebase.
After adding the services I want to retrieve their ID's and add them to "companies" as an array returned b first function if adding the "services".
const addedServicesIDs = await addServices(arrayOfServices);
await addCompany(newCompany, addedServicesIDs);
The services are added succesfully but I cannot retreive their ID's which I store in the addServices function and returning them as array.
The console.log is working properly.
async function addServices(props) {
const arrayOfServices = props;
const arrayOfServicesID = [];
arrayOfServices.forEach(async (service, index) => {
console.log(service);
await db
.collection("services")
.add({
serviceName: service.serviceDetails.serviceName,
description: service.serviceDetails.description,
duration: service.serviceDetails.duration,
price: service.serviceDetails.price,
capacity: service.serviceDetails.capacity,
workingDays: service.serviceDayWorking,
})
.then((docRef) => {
arrayOfServicesID[index] = docRef.id;
console.log("Written Service with ID of ", docRef.id);
});
});
return arrayOfServicesID;
}
Maybe I'm not understading that well async functions,
I will be very thankful for your help!
Finally I have found a solution.
I used const instead of var ,that's why my variable was not updating.
var AddedServicesIDs = [];
I have refactored my code
export async function addServices(props) {
const doc_ref = await db.collection("services").add({
serviceName: props.serviceDetails.serviceName,
description: props.serviceDetails.description,
duration: props.serviceDetails.duration,
price: props.serviceDetails.price,
capacity: props.serviceDetails.capacity,
workingDays: props.serviceDayWorking,
});
return doc_ref.id;
}
for (const service of arrayOfServices) {
const extractedID = await addServices(service);
AddedServicesIDs.push(extractedID);
}

How could I write this function so it doesn't setState within the foreach everytime

The function collects role Assignment PrincipalIds on an item in SPO. I then use a foreach to populate state with the Title's of these PrincipalIds. This all works fine but it's inefficient and I'm sure there is a better way to do it than rendering multiple times.
private _permJeChange = async () => {
if(this.state.userNames){
this.setState({
userNames: []
});
}
var theId = this.state.SelPermJEDD;
var theId2 = theId.replace('JE','');
var info = await sp.web.lists.getByTitle('MyList').items.getById(theId2).roleAssignments();
console.log(info, 'info');
var newArr = info.map(a => a.PrincipalId);
console.log(newArr, 'newArr');
// const userIds = [];
// const userNames = [];
// const userNameState = this.state.userNames;
newArr.forEach(async el => {
try {
await sp.web.siteUsers.getById(el).get().then(u => {
this.setState(prevState => ({
userNames: [...prevState.userNames, u.Title]
}));
// userNames.push(u.Title);
// userIds.push(el);
});
} catch (err) {
console.error("This JEForm contains a group");
}
});
}
I've left old code in there to give you an idea of what I've tried. I initially tried using a local variable array const userNames = [] but declaring it locally or even globally would clear the array everytime the array was populated! So that was no good.
PS. The reason there is a try catch is to handle any SPO item that has a permissions group assigned to it. The RoleAssignments() request can't handle groups, only users.
Create an array of Promises and await them all to resolve and then do a single state update.
const requests = info.map(({ PrincipalId }) =>
sp.web.siteUsers.getById(PrincipalId).get().then(u => u.Title)
);
try {
const titles = await Promise.all(requests);
this.setState(prevState => ({
userNames: prevState.userNames.concat(titles),
}));
} catch (err) {
console.error("This JEForm contains a group");
}

FS access api & React: duplicate entries

I'm trying to build a movie dashboard (something like Plex); the user selects a folder and all the movies he has in the folder show up. The use of the new File System Access API allows me to create file handlers on the fly and be able to display movies using the browser video player.
The problem I'm facing is with duplicated entries, for instance "Ghostbusters" (can't really understand why, but that's the only one causing the issue)
This is the basic implementation of the file system:
try {
const folderHandle = await window.showDirectoryPicker();
const addedFilms = [];
history.push('/list');
// const entries = await folderHandle.values()
const entries = await folderHandle.values();
for await (const entry of entries) {
const movie = await readMoviesonDisk(folderHandle, entry);
console.log(addedFilms);
if (addedFilms.includes(entry.name)) continue;
addedFilms.push(entry.name);
setMovies((movies) => [...movies, movie]);
}
} catch (error) {
alert('Alert from reading files: ' + error);
}
setMovies just sets a Context with a movies array and readMoviesOnDisk is the following:
const readMoviesonDisk = async (folderHandle, entry) => {
if (entry.kind === 'file' && entry.name.endsWith('.mp4')) {
const path = await folderHandle.resolve(entry);
const handle = await folderHandle.getFileHandle(path);
const movie = await getMovie(entry.name);
if (movie) {
return { ...movie.data, file: handle, name: entry.name };
}
const movieData = await searchMovie(entry.name);
if (movieData) {
const actualData = await getMovieDetails(movieData.id);
if (actualData !== undefined) {
await insertMovie(entry.name, actualData, handle);
} else {
await insertMovie(entry.name, actualData, handle);
}
return { ...actualData, file: handle, name: entry.name };
}
return { name: entry.name, file: handle };
}
};
searchMovie and insertMovie only interact with IndexedDB to store movie info for offline use. getMovieDetails does API calls to TMDB to get movie info.
The key I use for displaying the movies is their TMDB id. Ghostbusters' id is "620".
Can anyone help me?
Without additional background it seems impossible to answer this properly. Can you iterate over all files in the folder and just log the names and kinds? This should work and show no duplicate entries.
const dirHandle = await window.showDirectoryPicker();
for await (const entry of dirHandle.values()) {
console.log(entry.kind, entry.name);
}

Resources