React Firebase async return of IDs - reactjs

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);
}

Related

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

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.

react firestore sub collection

How can I get a list of cars for a customer
clients:
w21rffa3:
name: Johny
phone: 123123
cars:
fn1jnr12:
brand: AUDi
model: a6
number: 24f1
dsdasgf122:
brand: AUDi
model: a3
number: 62s14
My code
const ref = firestore().collection('clients');
const [clientsList, setClientsList] = useState([]);
useEffect(() => {
return ref.onSnapshot(clientsSnapshot => {
const clients = [];
const cars = [];
clientsSnapshot.forEach(client => {
const carsRef = ref.doc(client.id).collection('cars').onSnapshot(carsSnapshot => {
carsSnapshot.forEach(car => {
if (car.data().brand.length > 0) {
const {
brand,
model,
number
} = car.data();
cars.push({
id: car.id,
brand,
model,
number,
});
}
});
//Good result`
console.log('After forEach: ', cars);
});
//Bad result
console.log('After snapshot: ', cars);
const {
name,
phone
} = client.data();
clients.push({
id: client.id,
name,
phone,
cars: cars,
});
});
setClientsList(clients);
});
}, []);
cars list for customers
The error you facing is due to misuse/misunderstanding of how async/callback-based functions works. As I said in my comment - good result and bad result - bad result scripts are executed before good result due to onSnapshot is async, and you pass a callback function to it, which will be executed when data is available from firebase, so a bit "later" than the rest of the code.
Now about what can be done. The code is a bit tricky and I didnt really test it, so if anything - please, let me know.
const [clientsList, setClientsList] = useState([]);
useEffect(() => {
let carsUnsubscribeFns = [];
const clientUnsubscribeFn = ref.onSnapshot((clientsSnapshot) => {
// Reset everything and stop previously created listeners for Cars
setClientsList([]);
carsUnsubscribeFns.forEach((x) => x());
carsUnsubscribeFns = [];
clientsSnapshot.forEach((c) => {
const { name, phone } = c.data();
const client = { id: c.id, name, phone };
// In case you dont want to use optional chaining,
// remove the const client = ... line above
// and uncomment the line below
// but optional chaining is prefered anyway
// const client = { id: c.id, name, phone, cars: [] };
const carsUnsubscribeFn = ref
.doc(client.id)
.collection("cars")
.onSnapshot((carsSnapshot) => {
// Mutate the Client object directly
client.cars = carsSnapshot.docs
.map((x) => ({ id: x.id, ...x.data() }))
.filter((x) => x.brand?.length > 0);
// mark ClientsList as updated to force a rerender
// due to we mutated one of the entities inside
setClientsList((curr) => [...curr]);
});
carsUnsubscribeFns.push(carsUnsubscribeFn);
setClientsList((curr) => {
curr.push(client);
return [...curr];
});
});
// clean-up function returned from hook to stop all the listeners
return () => {
[clientUnsubscribeFn, ...carsUnsubscribeFns].forEach((x) => x());
};
});
}, []);

Saving an ID value from an API to a User with GraphQL

I'm working on a video game website where a user can save a game to a list. How this is supposed to work is when the user clicks "Complete Game", the ID of the game is saved to a state that holds the value. The value is then passed into the mutation, then the mutation runs, saving the ID of the game to the users list of completed games. However, all I'm seeing in the console is this:
"GraphQLError: Variable \"$addGame\" got invalid value { gameId: 740, name: \"Halo: Combat Evolved\",
The above error continues, listing the entirety of the API response, instead of just the gameId.
I was able to successfully add the game to the list in the explorer with the following mutation:
mutation completeGame($addGame: AddNewGame!) {
completeGame(addGame: $addGame) {
_id
completedGameCount
completedGames {
gameId
}
}
}
with the following variable:
{
"addGame": {"gameId": 740}
}
How can I trim down what is being passed into the mutation to just be the gameId?
Below is the entirety of the page, except the return statement at the bottom.
const [selectedGame, setSelectedGame] = useState([]);
const [savedGameIds, setSavedGameIds] = useState(getSavedGameIds());
const [completeGame, { error }] = useMutation(COMPLETE_GAME);
const { id: gameId } = useParams();
useEffect(() => {
return () => saveGameIds(savedGameIds);
});
useEffect(() => {
async function getGameId(gameId) {
const response = await getSpecificGame(gameId);
if (!response.ok) {
throw new Error('Something went wrong...');
}
const result = await response.json();
const gameData = result.map((game) => ({
gameId: game.id,
name: game.name,
cover: game.cover,
summary: game.summary,
platforms: game.platforms,
platformId: game.platforms,
genres: game.genres,
genreId: game.genres,
}));
setSelectedGame(gameData);
}
getGameId(gameId);
}, [])
const handleCompleteGame = async (gameId) => {
const gameToComplete = selectedGame.find((game) => game.gameId === gameId);
const token = Auth.loggedIn() ? Auth.getToken() : null;
if (!token) {
return false;
}
try {
const { data } = await completeGame({
variables: { addGame: { ...gameToComplete } },
});
console.log(data);
setSavedGameIds([...savedGameIds, gameToComplete]);
} catch (err) {
console.error(err);
}
};
With the mutation working in the explorer when I'm able to explicitly define the variable, I am led to believe that the issue is not with the resolver or the typedef, so I'm going to omit those from this post because I don't want it to get too long.
However, I'd be happy to attach any extra code (resolver, typeDef, getSavedGameIds function, etc) if it would allow anyone to assist. The issue (I think) lies in getting my response to match the syntax I used in the explorer, which means trimming down everything except the gameId.
I specifically am extremely suspicious of this line
const gameToComplete = selectedGame.find((game) => game.gameId === gameId)
but I have fiddled around with that for awhile to no avail.
Thank you to anyone who is able to help!
It sounds like you're trying to pass more into your mutation then your schema is defined to allow. In this part:
const { data } = await completeGame({
variables: { addGame: { ...gameToComplete } },
});
You're spreading gameToComplete here which means everything in the gameToComplete object is going to be sent as a variable. If your schema is setup to just expect gameId to be passed in, but your error message is showing that name is also being passed in, you just need to adjust your variables to exclude everything you can't accept. Try:
const { data } = await completeGame({
variables: { addGame: { gameId } },
});

Stripe refresh data in useElement hook

In form payment user has option to change duration of premium account (e.g from 1 month to 3 months). When user change it, front sent to api query to again create paymentIntents:
return await this.stripe.paymentIntents.create({
amount: 2000 * months,
currency: 'pln',
payment_method_types: ['p24', 'card'],
receipt_email: user.email,
description: 'premium',
});
this.stripe.paymentIntents return data:
amount: 4000
client_secret: "pi_3KTDMNDbx0KEJcOR289fojfP_secret_Mh5jPfAKjvxpeZCuXmRcjilFL"
id: "pi_3KTDMNDbx0KEJcOR289fojfP"
...
The problem is with hook useElements from #stripe/react-stripe-js library. Inside this hook still I have old id and when I click "Pay" I have old amount it's 2000, should be 6000.
How I can refresh data in useElements when user change count of months?
PS: I'm using this docs. Select 'Custom payment flow' and in backend I have Node (Nest.js), in front I have React.
Update:
Stripe method for payment:
import {
PaymentElement,
useStripe,
useElements,
} from '#stripe/react-stripe-js'
const Stripe = () => {
const stripe = useStripe()
const elements = useElements()
const sendPaymentDetails = async data => {
if (!stripe || !elements) return
setIsLoading(true)
const { name, email } = data
await stripe.confirmPayment({
elements,
confirmParams: {
return_url: `${APPLICATION_URL}premium`,
payment_method_data: {
billing_details: {
name,
email,
},
},
},
})
}
return (
<form onSubmit={sendPaymentDetails}>
<PaymentElement />
</form>
)
}
and this is method to create paymentIntent:
const [stripeOptions, setStripeOptions] = useState(null)
const paymentIntent = async months => {
const { data } = await createPaymentIntent(months);
setStripeOptions(data);
};
when I create new paymentIntent, should update too useElement data
I found resolve.I should update paymentIntents, not create new paymentIntents. So I add method:
return await this.stripe.paymentIntents.update(id, {
amount: this.amount * months,
});
from this docs
Now inside useElements hook I have correctly data

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");
}

Resources