My messages disappear I get a new message or send messages - reactjs

I've been facing this problem.
My code now disappears when get a new message. I want to make messages pile up, but I have tried many ways, but I have not solved them. I would really appreciate it if you could tell me how to do this.
When I enter a message room, I get 25 messages from firebase server.
If there are more than 25 messages, last message disappears.
Disappear message. I don't want to like this ....
I want to like this.
here is my disappear code
const messageLimit = 25;
const [messagesSnapshot] = useCollection(
db
.collection('chats')
.doc(id)
?.collection('messages')
.orderBy('timestamp', 'desc')
.limit(messageLimit),
);
if (messagesSnapshot) {
const snap = messagesSnapshot.docs;
const startPost = messagesSnapshot.docs[messagesSnapshot.docs.length - 1];
setStartAt(startPost);
const messages = snap.map(message => ({
id: message.id,
user: message.data().user,
messageInfo: {
...message.data(),
timestamp: message.data().timestamp?.toDate().getTime(),
},
}));
setMessagesList(messages);
}
const getMoreMessages = async () => {
if (!lastPost) {
setIsGetMessagesLoading(true);
const query = await db
.collection('chats')
.doc(id)
.collection('messages')
.orderBy('timestamp', 'desc')
.startAfter(startAt)
.limit(messageLimit)
.get();
const messages = query.docs.map(message => ({
id: message.id,
user: message.data().user,
messageInfo: {
...message.data(),
timestamp: message.data().timestamp?.toDate().getTime(),
},
}));
setStartAt(query.docs[query.docs.length - 1]);
setMessagesList([...messagesList, ...messages]);
setIsGetMessagesLoading(false);
messages.length === 0 ? setLastPost(true) : setLastPost(false);
}
};
useEffect(() => {
showMessages();
}, [messagesSnapshot]);

As Shyam commented, you've limited the query to 25 messages, so when a new message is added, the oldest message falls out of the query.
One thing you could do is detect the changes between the updates, and never remove the "outdated" documents.
The example from the documentation:
db.collection("cities").where("state", "==", "CA")
.onSnapshot((snapshot) => {
snapshot.docChanges().forEach((change) => {
if (change.type === "added") {
console.log("New city: ", change.doc.data());
}
if (change.type === "modified") {
console.log("Modified city: ", change.doc.data());
}
if (change.type === "removed") {
console.log("Removed city: ", change.doc.data());
}
});
});
So you'd only handled added (which is initially every document) and possibly modified, but ignore removed.

Related

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());
};
});
}, []);

React Firestore - Retrieve all documents in an Array of document IDs

I am trying to retrieve an all documents of which document ID is within the given array. I have no issues retrieving the list of documentIDs and storing in the array. However, I am not sure on how to retrieve the documents of which ids are in the array. Please help Thanks!
portfolio doc:
docId{
docId: docId,
...
}
const getData = (bookmarkIds) => {
console.log("this is " )
console.log(bookmarkIds)
console.log("entering" )
const portfolioQuery = database.portfolioRef.where("docId", 'in', bookmarkIds);
portfolioQuery.onSnapshot((snapshot) => {
console.log(snapshot.docs)
if (snapshot.docs.length !== 0) {
var id = 1;
const tempItem = [];
snapshot.docs.forEach((doc) => {
tempItem.push({
id: id,
intervieweeName: doc.data().intervieweeName,
intervieweeEmail: doc.data().intervieweeEmail,
projectTitle: doc.data().projectTitle,
portfolioTitle: doc.data().portfolioTitle,
dateCreated: doc.data().dateCreated,
fbId: doc.id
})
id++;
})
setPortfolioData(tempItem)
}
})
}
useEffect(() => {
const getUserData = database.usersRef.where("email", "==", currentUser.email);
const bookmarkArray = [];
const unsub = getUserData.onSnapshot((snapshot) => {
snapshot.docs.forEach((doc) =>{
bookmarkArray.push(doc.data().bookmarkIds);
})
console.log(bookmarkArray);
getData(bookmarkArray)
})
return unsub;
}, [currentUser.email]);
Based on my current code above, I am receiving the following (Not getting any error just a blank return):
I realised I made a double array by doing
bookmarkArray.push(doc.data().bookmarkIds);
solved by doing
bookmarkArray = doc.data().bookmarkIds;

Uncaught TypeError: Cannot read properties of undefined but is defined

The purpose of this application is to make an API call to google places API, gather info about a restaurant, then display to the user.
The application works for the most part but every maybe 5-10 API calls on average the app crashes.
The error:
The code:
// State and global variables //
const [searchResponse, setSearchResponse] = useState("");
const [secondarySearchResponse, setsecondarySearchResponse] = useState("");
const [information, setInformation] = useState("");
const [secondaryInformation, setSecondaryInformation] = useState("");
const [itemFilter, setFilter] = useState("");
const [place_id, setPlaceId] = useState("");
const [dataReady, setDataReady] = useState(false);
const [locationHad, setLocationHad] = useState(false);
const pos = useRef(null);
const key = "AIzaSyD1ZTsmbDBBlMpmaogO_hlj93zzbDDtAoc";
var num = Math.floor(Math.random() * 20 + 1);
// Use Effects
// Gets users current location
useEffect(() => {
navigator.geolocation.getCurrentPosition((position) => {
pos.current = position;
console.log("Location had. Ready for API call.");
setLocationHad(true);
});
}, []);
// Once we have clicked our button and made our api call, we get the place_id and save it so we can make a secondary api call using place_id
useEffect(() => {
if (
searchResponse !== "" &&
searchResponse.results[num].place_id !== undefined) {
setPlaceId(searchResponse.results[num].place_id);
console.log("place_id set");
} else {
console.log("error in setting place_id");
}
}, [searchResponse]);
// One place_id is set we make secondary api call
useEffect(() => {
if (place_id !== "") {
fetchSecondaryInfo();
} else {
console.log("no place id!");
}
}, [place_id]);
// Now that we have made both api calls we save the relavent info into state that we will pass down to child components
useEffect(() => {
if (searchResponse !== "") {
console.log(searchResponse.results[num].name);
setInformation({
name: searchResponse.results[num].name,
open_now: searchResponse.results[num].opening_hours.open_now,
rating: searchResponse.results[num].rating,
price: searchResponse.results[num].price_level,
location: {
lat: searchResponse.results[num].geometry.location.lat,
lng: searchResponse.results[num].geometry.location.lng,
},
});
console.log("info set!");
} else {
console.log("no info to set!");
}
}, [searchResponse]);
// And again for the secondary info (I broke this dwown into to seperate useEffects trying to figure out what was causing my error...)
useEffect(() => {
if (secondarySearchResponse !== "") {
setSecondaryInformation({
phone_number: secondarySearchResponse.result.formatted_phone_number,
daily_hours: secondarySearchResponse.result.opening_hours.weekday_text,
address: secondarySearchResponse.result.formatted_address,
});
setDataReady(true);
console.log("secondary info set!");
} else {
console.log("no secondary info to set!");
}
}, [secondarySearchResponse]);
// Function that makes api call
async function fetchInfo() {
if (locationHad) {
if (itemFilter === "") {
var url = `https://secure-dawn-88985.herokuapp.com/https://maps.googleapis.com/maps/api/place/nearbysearch/json?location=${pos.current.coords.latitude},${pos.current.coords.longitude}&radius=12000&type=restaurant&key=${key}`;
} else {
var url = `https://secure-dawn-88985.herokuapp.com/https://maps.googleapis.com/maps/api/place/nearbysearch/json?keyword=${itemFilter[0]}&location=${pos.current.coords.latitude},${pos.current.coords.longitude}&radius=12000&type=restaurant&key=${key}`;
}
await fetch(url)
.then((response) => response.json())
.then((data) => setSearchResponse(data))
.then(console.log("api request fired."));
} else {
console.log("location not yet identified!");
}
}
// Function that makes secondary api call
async function fetchSecondaryInfo() {
if (place_id !== "") {
const secondary_url = `https://secure-dawn-88985.herokuapp.com/https://maps.googleapis.com/maps/api/place/details/json?fields=formatted_phone_number,opening_hours,formatted_address&place_id=${place_id}&key=${key}`;
await fetch(secondary_url)
.then((response) => response.json())
.then((data) => setsecondarySearchResponse(data))
.then(console.log("secondary api request fired."));
} else {
console.log("place_id not had in secondary fetch.");
}
}
As for the place_id error I put in the a specific line of code to avoid this error:
useEffect(() => {
if (
searchResponse !== "" &&
searchResponse.results[num].place_id !== undefined
) {
console.log(searchResponse.results[num].place_id);
setPlaceId(searchResponse.results[num].place_id);
console.log("place_id set");
} else {
console.log("error in setting place_id");
}
}, [searchResponse]);
So I do not understand how its possible to even throw this error with that line in there.
As for the name error I put in a specific line to console log the object before it reads the properties but it doesn't print in the console before throwing the error:
useEffect(() => {
if (searchResponse !== "") {
console.log(searchResponse.results[num].name);
setInformation({
name: searchResponse.results[num].name,
open_now: searchResponse.results[num].opening_hours.open_now,
rating: searchResponse.results[num].rating,
price: searchResponse.results[num].price_level,
location: {
lat: searchResponse.results[num].geometry.location.lat,
lng: searchResponse.results[num].geometry.location.lng,
},
});
console.log("info set!");
..........
I appreciate any input, suggestions, critiques, etc.
The error message shows that the error is being thrown on this line:
searchResponse.results[num].place_id !== undefined
This will throw if searchResponse.results[num] doesn't exist.
To be concise, try using optional chaining (and initialize searchResponse to undefined or null). Do
const [searchResponse, setSearchResponse] = useState();
and change
if (
searchResponse !== "" &&
searchResponse.results[num].place_id !== undefined) {
setPlaceId(searchResponse.results[num].place_id);
to
const possiblePlaceId = searchResponse?.results[num]?.place_id;
if (possiblePlaceId) {
setPlaceId(possiblePlaceId);

Cant get the reactions to recognise on a specific message now

In a pervious post I was having issues getting the bot to recognise reactions and the fix worked however and then changed it to react on a message that the bot says afterwards and now it isnt working again, I have tried changing the user condition so its the original command author but that didn't seem to work
So you run the code and it makes the embed perfectly and reacts to it however it doesnt recognise when you react and makes the timeout message
exports.run = async (client, message, args) => {
message.delete()
const MINUTES = 5;
const questions = [
{ answer: null, field: 'placeholdquestion' },
{ answer: null, field: 'placeholdquestion' },
{ answer: null, field: 'placeholdquestion' },
]; //to add more questions just add another line of the above code {answes: null, field: `Question Here`}
let current = 0;
const commanduser = message.author.id
// ...
// wait for the message to be sent and grab the returned message
// so we can add the message collector
const sent = await message.author.send(
`${questions[current].field}`,
);
const filter = (response) => response.author.id === message.author.id;
// send in the DM channel where the original question was sent
const collector = sent.channel.createMessageCollector(filter, {
max: questions.length,
time: MINUTES * 60 * 1000,
});
// fires every time a message is collected
collector.on('collect', (message) => {
//if (questions > 1 && questions < 10) {
// add the answer and increase the current index HERE
questions[current++].answer = message.content;
const hasMoreQuestions = current < questions.length; //change to be an imput of how many questions you want asked
if (hasMoreQuestions) {
message.author.send(
`${questions[current].field}`,
);
}
});
// fires when either times out or when reached the limit
collector.on('end', (collected, reason) => {
if (reason === 'time') {
return message.author.send(
`I'm not saying you're slow but you only answered ${collected.size} questions out of ${questions.length} in ${MINUTES} minutes. I gave up.`,
);
}
const embed = new MessageEmbed()
.setTitle("LOA Request")
.addFields(
{ name: 'placehold', value: questions[0].answer+'/10' },
{ name: 'placehold', value: questions[1].answer+'/10' },
{ name: 'placehold', value: questions[2].answer+'/10', inline: true },)
.setColor(`#1773BA`)
.setTimestamp()
.setThumbnail("https://media.discordapp.net/attachments/772915309714735205/795378037813805126/mvg_clean_2.png")
.setFooter("request by: " + message.author.tag);
;
message.channel.send(embed)
.then(function (message) {
message.react("👍")
message.react("👎")})
const filter = (reaction, user) => {
return ['👍', '👎'].includes(reaction.emoji.name) && user.id === commanduser; //changed to try and fix it didnt work as message.author.id or this
};
message.awaitReactions(filter, { max: 1, time: 60000, errors: ['time'] } )
.then(collected => {
const reaction = collected.first();
if (reaction.emoji.name === '👍') {
message.channel.send('you reacted with a thumbs up.');
}
else {
message.reply('you reacted with a thumbs down.');
}
})
.catch(collected => {
console.log(`After a minute, only ${collected.size} out of 4 reacted.`);
message.reply('you didn\'t react with neither a thumbs up, nor a thumbs down.');
});
});
;
}
You have a slight logic error. You need to fit the code from your second filter to the message.awaitReactions inside of your message.channel.send(embed).then(function (message)...) method. In your code, the bot is trying to check for reactions from the original message, which you already deleted (since the awaitReactions is outside the scope of your function where you send and react to the embed).
Like this:
message.channel.send(embed)
.then(function (message) {
message.react("👍")
message.react("👎")
const filter2 = (reaction, user) => {
return ['👍', '👎'].includes(reaction.emoji.name) && user.id === commanduser;
};
message.awaitReactions(filter2, { max: 1, time: 60000, errors: ['time'] })
.then(collected => {
const reaction = collected.first();
if (reaction.emoji.name === '👍') {
message.channel.send('you reacted with a thumbs up.');
}
else {
message.reply('you reacted with a thumbs down.');
}
})
.catch(collected => {
console.log(`After a minute, only ${collected.size} out of 4 reacted.`);
message.reply('you didn\'t react with neither a thumbs up, nor a thumbs down.');
});
})

React State is rolling back to previous value

I have a problem which I could not figure out. I have this function which calculates the total price:
const [totalPrice, setTotalPrice] = useState(0);
const calcTotalPrice = (itemss) => {
var sum = null;
itemss.forEach(function (item) {
sum += item?.Quantity * item?.Prijs;
});
setTotalPrice(sum);
console.log(totalPrice);
};
It works great, but when I update the quantity of a product the state does not update. Well, it does but then rolls back to it's previous value. Here is the output I am getting:
570
443
467
Which is very weird. The top value is the new value, the second is the old and I don't know where the third one is coming from. The function is called everytime the function getItems() is called:
async function getItems() {
setLoading(true);
db.collection(`rooms/${roomId}/Items`).onSnapshot((querySnapshot) => {
const itemss = [];
querySnapshot.forEach((doc) => {
itemss.push(doc.data());
});
setItems(itemss);
calcTotalPrice(itemss);
setLoading(false);
});
}
useEffect(() => {
getItems();
}, []);
I don't get what I am doing wrong here. Any help would be much appreciated.
setTotalPrice(sum); won't reflect totalPrice immediately because setting state function is asynchronous. That's why console.log doesn't seem to be working properly.
Also, onSnapshot listener will be called every time collection data is changed (added/modified/removed). Here's an example for your information.
db.collection("cities").where("state", "==", "CA")
.onSnapshot(function(snapshot) {
snapshot.docChanges().forEach(function(change) {
if (change.type === "added") {
console.log("New city: ", change.doc.data());
}
if (change.type === "modified") {
console.log("Modified city: ", change.doc.data());
}
if (change.type === "removed") {
console.log("Removed city: ", change.doc.data());
}
});
});

Resources