data position with firebase - javascript

I'm trying to keep in position the data that I'm getting from firebase but when i refresh the page it's showed up in diferent positions and not in his created position
I thought use 'position' and 'beforeend' but the result it's the same
for example I post (a,b,c,d,e) and when I refresh the page it's shows (e,d,a,b,c) here some pictures enter image description here
const addForm = document.querySelector(".add");
const list = document.querySelector(".todos");
const search = document.querySelector(".search input");
// generate new toDo's
const generateTemplate = (toDo, id) => {
let html = ` <li data-id=${id} class="list-group-item d-flex justify-content-between align-items-center">
<span>${toDo.title}</span><i class="far fa-trash-alt delete"></i>
</li>`;
const position = "beforeend";
list.insertAdjacentHTML(position, html);
};
const deleteTodo = (id) => {
const todos = document.querySelectorAll("li");
todos.forEach((toDo) => {
if (toDo.getAttribute("data-id") === id) {
toDo.remove();
}
});
};
// get the info in the page
db.collection("Todos").onSnapshot((snapshot) => {
snapshot.docChanges().forEach((change) => {
const doc = change.doc;
if (change.type === "added") {
generateTemplate(doc.data(), doc.id);
} else if (change.type === "removed") {
deleteTodo(doc.id);
}
});
});
// submit the todo
addForm.addEventListener("submit", (e) => {
e.preventDefault();
const now = new Date();
const toDo = {
title: addForm.add.value.trim(),
created_at: firebase.firestore.Timestamp.fromDate(now),
};
db.collection("Todos")
.add(toDo)
.then(() => {
console.log("todo added");
})
.catch((err) => {
console.log(err);
});
addForm.reset();
});
// delete todo's
list.addEventListener("click", (e) => {
if (e.target.classList.contains("delete")) {
const id = e.target.parentElement.getAttribute("data-id");
db.collection("Todos")
.doc(id)
.delete()
.then(() => {
console.log("Todo Deleted");
});
}
});
// search todo's
const filterTodos = (term) => {
Array.from(list.children)
.filter((todo) => !todo.textContent.toLowerCase().includes(term))
.forEach((todo) => todo.classList.add("filtered"));
Array.from(list.children)
.filter((todo) => todo.textContent.toLowerCase().includes(term))
.forEach((todo) => todo.classList.remove("filtered"));
};
search.addEventListener("keyup", () => {
const term = search.value.trim().toLowerCase();
filterTodos(term);
});
db.collection("Todos")
.get()
.then((snapshot) => {
snapshot.docs.forEach((doc) => console.log(doc.data()));
});

You need to use orderBy to fetch documents with sorting.
It should look like this.
db.collection("Todos")
.orderBy('created_at')
.get()
.then((snapshot) => {
snapshot.docs.forEach((doc) => console.log(doc.data()));
});

Related

useEffect efficiency in Star Wars API

I need some help with me current project making in React. I'am making a star-wars-app for my job interview and I stucked on a one problem.
Fetch efficiency.
I'am fetching this data, and then fetching some more because of the url's in the first fetched data, and everything fetches good, but first i have the 'url's' seeing in the table and then it changes into correct data.
I want to set the 'fetched' state to true when everything is rendered correctly but I don't know how to do it.
const api = `https://swapi.dev/api/people/`;
const [characters, setCharacters] = useState([]);
const [speciesOptions, setSpeciesOptions] = useState([]);
const [homeworldOptions, setHomeworldOptions] = useState([]);
const [fetched, setFetched] = useState(false);
useEffect(() => {
const fetchedTimeout = () => {
setTimeout(() => {
setFetched(true);
}, 2000);
};
const fetchArray = (array, arrName) => {
for (let elem of array) {
fetch(elem).then((response) =>
response.json().then((data) => {
array.shift();
array.push(data.name);
})
);
}
if (arrName === "species") {
if (!array.length) {
array.push("Unspecified");
}
}
};
async function fetchOtherData(characters) {
await characters.forEach((character) => {
const homeworld = character.homeworld;
const vehicles = character.vehicles;
const starships = character.starships;
const species = character.species;
fetch(homeworld).then((response) =>
response.json().then((data) =>
setCharacters((prevData) =>
prevData.map((prevCharacter) =>
prevCharacter.homeworld === homeworld
? {
...prevCharacter,
homeworld: data.name,
}
: prevCharacter
)
)
)
);
fetchArray(vehicles);
fetchArray(starships);
fetchArray(species, "species");
});
await setCharacters(characters);
await fetchedTimeout();
}
const fetchAllCharacters = (allCharacters, data) => {
if (data.next) {
fetch(data.next)
.then((response) => response.json())
.then((data) => {
allCharacters.push(...data.results);
fetchAllCharacters(allCharacters, data);
});
}
if (!data.next) {
fetchOtherData(allCharacters);
}
};
async function fetchApi() {
const allCharacters = [];
await fetch(api)
.then((response) => response.json())
.then((data) => {
allCharacters.push(...data.results);
fetchAllCharacters(allCharacters, data);
})
.catch((error) => console.log(error));
}
const setSpeciesFiltering = () => {
const speciesFiltering = [];
for (let character of characters) {
const characterSpecies = character.species.join();
const foundSpecies = speciesFiltering.indexOf(characterSpecies);
if (foundSpecies === -1) {
speciesFiltering.push(characterSpecies);
}
}
const speciesOptions = speciesFiltering.map((species) => (
<option value={species}>{species}</option>
));
setSpeciesOptions(speciesOptions);
};
const setHomeworldFiltering = () => {
const homeworldFiltering = [];
for (let character of characters) {
const characterHomeworld = character.homeworld;
const foundSpecies =
homeworldFiltering.indexOf(characterHomeworld);
if (foundSpecies === -1) {
homeworldFiltering.push(characterHomeworld);
}
}
const homeworldOptions = homeworldFiltering.map((homeworld) => (
<option value={homeworld}>{homeworld}</option>
));
setHomeworldOptions(homeworldOptions);
};
fetchApi();
setSpeciesFiltering();
setHomeworldFiltering();
}, []);
I will appreciate your response.
Okay, after all the comments (thanks for that!) i changed the code to something like this.
useEffect(() => {
const fetchOtherData = (characters) => {
const charactersWithAllData = [];
characters.forEach((character) => {
const homeworld = character.homeworld;
const species = character.species;
const vehicles = character.vehicles;
const starships = character.starships;
let urls = [homeworld, ...species, ...vehicles, ...starships];
Promise.all(
urls.map((url) => {
if (url.length) {
fetch(url)
.then((response) => response.json())
.then((data) => {
if (url.search("species") > 0) {
character.species = data.name;
}
if (url.search("planets") > 0) {
character.homeworld = data.name;
}
if (url.search("vehicles") > 0) {
character.vehicles.shift();
character.vehicles.push(data.name);
}
if (url.search("starships") > 0) {
character.starships.shift();
character.starships.push(data.name);
}
})
.catch((err) => console.error(err));
}
if (!url.length) {
if (url.search("species")) {
character.species = "Unspecified";
}
if (url.search("vehicles")) {
character.vehicles = "";
}
if (url.search("starships")) {
character.starships = "";
}
}
})
).then(charactersWithAllData.push(character));
});
return charactersWithAllData;
};
const fetchApi = () => {
const characters = [];
Promise.all(
[api].map((api) =>
fetch(api)
.then((response) => response.json())
.then((data) => characters.push(...data.results))
.then((data) => {
setCharacters(fetchOtherData(characters));
})
)
);
};
fetchApi();
}, []);
In what point do i have to set the 'characters' state ? Because in the situation above the data first shows on the screen, and then the state is set.
As other comments say, using Promise.all and refactoroing your useEffect is best solution for this.
But this might be helpful if you don't want to change a lot.
(but still consider refactor your hook)
const [loading, setLoading] = useState(0);
const isLoading = loading > 0;
// replace your fetches with below:
const myFetch = async (path) => {
try {
setLoading(loading => loading + 1);
return await fetch(path);
} finally {
setLoading(loading => loading - 1);
}
};
useEffect(() => {
// do your stuffs
}, []);

Firebase when add/delete data, app do functions more than once

I have problems with my money app. When I add/delete data from my app (products collection), my app do function "sumPrices()" more than one. For example: When I add one product, make once, add another product, make twice, add another product make three etc. This happen in the same way with delete data.
A do something wrong in my code?
Callback.push push data do array where I unsubscribe events from firebase.
AddStatsUI add UI to my DOM.
index.js:
// delete products
const handleTableClick = e => {
console.log(e); // mouseevent
if (e.target.tagName === 'BUTTON'){
const id = e.target.parentElement.parentElement.getAttribute('data-id');
db.collection('users')
.doc(user.uid)
.collection('products')
.doc(id)
.delete()
.then(() => {
// show message
updateMssg.innerText = `Product was deleted`;
updateMssg.classList.add('act');
setTimeout(() => {
updateMssg.innerText = '';
updateMssg.classList.remove('act');
}, 3000);
productUI.delete(id);
products.sumPrices(user.uid, callbacks).then(value => {
sumStats.addStatsUI('','');
const unsubscribe = db.collection('users').doc(user.uid).get().then(snapshot => {
sumStats.addStatsUI(value[0], snapshot.data().budget);
})
callbacks.push(unsubscribe);
});
})
}
}
table.addEventListener('click', handleTableClick);
callbacks.push(() => table.removeEventListener('click', handleTableClick))
//add new products to firebase
const handleExpenseFormSubmit = e => {
e.preventDefault();
const name = expenseForm.productName.value.trim();
const price = Number(expenseForm.price.value.trim());
console.log(`Product added: ${name}, ${price}`);
const user = firebase.auth().currentUser.uid;
products.addProduct(name, price, user)
.then(() => {
products.sumPrices(user, callbacks).then(value => {
sumStats.addStatsUI('','');
const unsubscribe = db.collection('users').doc(user).onSnapshot(snapshot => {
sumStats.addStatsUI(value, snapshot.data().budget);
})
callbacks.push(unsubscribe);
});
expenseForm.reset()
})
.catch(err => console.log(err));
}
expenseForm.addEventListener('submit', handleExpenseFormSubmit);
callbacks.push(() => expenseForm.removeEventListener('submit', handleExpenseFormSubmit))
product.js:
class Product {
constructor(name, price, budget, user) {
this.products = db.collection('users');
this.budget = budget;
this.name = name;
this.price = price;
this.user = user;
}
async addProduct(name, price, user) { //dodaje produkt do firebase
const now = new Date();
const product = {
name: name,
price: price,
created_at: firebase.firestore.Timestamp.fromDate(now),
};
const response = await this.products.doc(user).collection('products').add(product);
return response;
}
getProducts(callback, user){ //download list from firebase
this.products.doc(user).collection('products')
.orderBy("created_at", "desc")
.onSnapshot(snapshot => {
snapshot.docChanges().forEach(change => {
if(change.type === 'added'){
//udpate UI
return callback(change.doc.data(), change.doc.id);
}
});
});
}
updateBudget(budget, user){
this.budget = budget;
db.collection('users').doc(user).update({budget: budget});
// callbacks.push(unsubscribe);
}
async sumPrices(user, callbacks){
let finish = [];
const unsubscribe = this.products.doc(user).collection('products').onSnapshot(snapshot => {
let totalCount = 0;
snapshot.forEach(doc => {
totalCount += doc.data().price;
});
const a = totalCount;
console.log(a);
finish.push(a);
return finish;
})
callbacks.push(unsubscribe);
return finish;
};
};
sumStatsUI.js:
class Stats {
constructor(stats, circle, budget){
this.stats = stats;
this.circle = circle;
this.budget = budget;
}
addStatsUI(data, budget){
if(data) {
const outcome = Math.round(data * 100) / 100;
const sumAll = Math.round((budget - outcome) * 100) / 100;
this.stats.innerHTML += `
<div><span class="budget-name">Budget: </span> <span class="stat-value">${budget}$</span></div>
<div><span class="budget-name">Outcome: </span> <span class="stat-value outcome-value">${outcome}$</span></div>
<div><span class="budget-name">All: </span> <span class="stat-value last-value">${sumAll}$</span></div>
`;
const circle = Math.round(((outcome * 100) / budget) * 100) / 100;
this.circle.innerHTML += `${circle}%`;
} else {
this.stats.innerHTML = '';
this.circle.innerHTML = '';
}};
};
export default Stats;
I add console.log to sumPrices
App screenshot, when I add 2 products and try update budget
Okey, a add some improvement to my code, but still have problems with subscriptions. Now everything it's okey, but when I log out and log in functions getProducts() and updateBudget() no unsubscribe.
Code here:
index.js:
//get the products and render
const unsubscribe = products.getProducts((data, id) => {
console.log(data, id);
productUI.render(data, id);
}, user.uid);
callbacks.push(unsubscribe);
//update budget + form
const handleBudgetFormSubmit = e => {
e.preventDefault();
//update budget
const budget = Number(budgetForm.budget_value.value.trim());
sumStats.addStatsUI('', '');
products.updateBudget(budget, user.uid);
//reset form
budgetForm.reset();
const budgetCart = document.querySelector('#budget');
budgetCart.classList.remove('active');
// show message
updateMssg.innerText = `Your budget was updated to ${budget}$`;
updateMssg.classList.add('act');
setTimeout(() => {
updateMssg.innerText = '';
updateMssg.classList.remove('act');
}, 3000);
};
budgetForm.addEventListener('submit', handleBudgetFormSubmit);
callbacks.push(() =>
budgetForm.removeEventListener('submit', handleBudgetFormSubmit)
);
and else to onAuthStateChanged() -> if(user):
} else {
console.log('user logged out');
authUI('');
productUI.render('');
sumStats.addStatsUI('');
console.log('Callbacks array', callbacks);
callbacks.forEach(callback => callback());
callbacks.length = 0;
}
});
getProducts() and updateBudget():
getProducts(callback, user) {
//download list from firebase
this.products
.doc(user)
.collection('products')
.orderBy('created_at', 'desc')
.onSnapshot(snapshot => {
snapshot.docChanges().forEach(change => {
if (change.type === 'added') {
//udpate UI
return callback(change.doc.data(), change.doc.id);
}
});
});
}
updateBudget(budget, user) {
console.log('budget', budget, user);
const db = firebase.firestore();
// this.budget = budget;
db.collection('users')
.doc(user)
.update({ budget: budget });
}
When I log out and log in:
When I have getProducts and add product to collection, this function render (render()) product twice, but add to collection once. When I update budget this return budget but after that, return 0 (on DOM where show budget a can see "Infinity")
And one thing, when I log out, console return error:
TypeError: callback is not a function
at eval (index.js:182)
at Array.forEach (<anonymous>)
at Object.eval [as next] (index.js:182)
at eval (index.cjs.js:1226)
at eval (index.cjs.js:1336)
I think it's because getProducts and updateBudget don't return unsubscribe, but undefined.
Maybe someone have solution for this?

Comments block realization with js

I need to do a block with comments to the photos. Comments are taken from the server and i render them like this:
class Comment {
constructor(date, text){
this.commentElement = this.create(date, text);
}
create(dateValue, textValue){
const commentItem = document.createElement('div');
commentItem.classList.add('popup__comment');
commentItem.classList.add('comment');
const commentDate = document.createElement('span');
commentDate.classList.add('comment__date');
commentDate.textContent = dateValue;
const commentText = document.createElement('span');
commentText.classList.add('comment__text');
commentText.textContent = textValue;
commentItem.appendChild(commentDate);
commentItem.appendChild(commentText);
return commentItem;
}
}
class CommentList {
constructor(container, items) {
this.container = container;
this.items = items;
this.render();
}
addComment(date, text){
const {commentElement} = new Comment(date, text);
this.container.appendChild(commentElement);
}
render(){
console.log(this.items);
this.items.comments.forEach((comment) => {
const dateToFormat = new Date(comment.date);
const year = dateToFormat.getFullYear();
const month = dateToFormat.getMonth();
const date = dateToFormat.getDate();
const finalDate = `${date}.${month + 1}.${year}`;
this.addComment(finalDate, comment.text);
})
}
}
let commentList;
api.getPictures()
.then((data) => {
data.forEach((item) => {
fetch(`example.com`)
.then(res => res.json())
.then((data) => {
if (data.comments && data.comments.length) {
commentList = new CommentList(commentContainer, data)
}
})
.catch((err) => {
console.log(err);
})
})
})
.catch((err) => console.log(err));
The problem is, only 2 of 6 photos have comments, but all comments render under all the photos. How can i render comments only with photos they belong? Console.log(this.items) shows the exactly that objects with comments, which i need though

How To Save Object Of Images To Real-time Firebase

I want to save a bunch of Images to Firebase storage and it's saved very well "as known image by image " in Firebase Storage, so after I saved it I want to get all the Uri and put it into Real-time DB as an Array-object like this
but I'm tried here in this code and also save one image just like this!
So how to handle these to Get all the images in the Storage and then put them into an array in DB?
// Open Gallery
pickMultiple = () => {
ImagePicker.openPicker({
multiple: true
})
.then(images => {
this.setState({
images: images.map(i => {
return {
uri: i.path,
width: i.width,
height: i.height,
mime: i.mime
};
})
});
})
.catch(e => console.log(e));
};
_SaveImagesToFirebase = () => {
const uid = firebase.auth().currentUser.uid; // Provider
const { images } = this.state;
const provider = firebase.database().ref(`providers/${uid}`);
images.map(image => {
let file = image.uri;
const path = "Img_" + Math.floor(Math.random() * 1500 + ".jpg");
const ref = firebase
.storage()
.ref(`provider/${uid}/ProviderGalary/${path}`);
let imagesArray = [];
ref
.put(file)
.then(() => {
ref
.getDownloadURL()
.then(
images => {
console.log(images);
imagesArray.push({
uri: images
});
},
error => console.log(error)
)
.then(() => {
provider
.update({
Images: imagesArray
})
.then(() => console.log("done with imgs"));
});
console.log("#inside", imagesArray);
})
.then(() => {
setTimeout(() => {
this.props.navigation.navigate("Home");
}, 2000);
});
console.log("#OUT", imagesArray);
});
};
UH My bad, I just define imagesArray inside map() it should be outside! like this,
_SaveImagesToFirebase = () => {
const uid = firebase.auth().currentUser.uid; // Provider
const { images } = this.state;
const provider = firebase.database().ref(`providers/${uid}`);
=> let imagesArray = [];
images.map(image => {
let file = image.uri;
const path = "Img_" + Math.floor(Math.random() * 1500 + ".jpg");
const ref = firebase
.storage()
.ref(`provider/${uid}/ProviderGalary/${path}`);
ref
.put(file)
.then(() => {
ref
.getDownloadURL()
.then(
images => {
console.log(images);
imagesArray.push({
uri: images
});
},
error => console.log(error)
)
.then(() => {
provider
.update({
Images: imagesArray
})
.then(() => console.log("done with imgs"));
});
})
.then(() => {
setTimeout(() => {
this.props.navigation.navigate("Home");
}, 2000);
});
});
};

Why am I unable to re-add data with the same name after removing it with Firebase?

In my app, users are able to add a loved one, like so:
addLovedOne(event) {
const {
lovedOne,
} = this.state;
const unsubscribe = firebase.auth().onAuthStateChanged((user) => {
if (user) {
db.addLovedOne(user.uid, lovedOne)
.then(() => {
this.setState({ lovedOne: '' });
this.refreshLovedOnes();
})
.catch(error => {
this.setState(byPropKey('error', error));
});
} else {
unsubscribe();
}
});
event.preventDefault();
}
Naturally, users are able to remove loved ones, like so:
removeLovedOne(event) {
const lovedOne = event.target.id;
const unsubscribe = firebase.auth().onAuthStateChanged((user) => {
if (user) {
const ref = firebase.database().ref(`users/${user.uid}/lovedOnes`);
const removeLovedOne = ref.orderByChild('lovedOne').equalTo(lovedOne);
removeLovedOne.on('value', (snapshot) => {
const lovedOneId = snapshot.node_.children_.root_.key;
db.removeLovedOne(user.uid, lovedOneId, lovedOne)
.then(() => {
this.refreshLovedOnes();
})
.catch(error => {
this.setState(byPropKey('error', error));
});
});
} else {
unsubscribe();
}
});
}
Here is what the queries look like:
export const addLovedOne = (userId, lovedOne) => (
db.ref(`users/${userId}/lovedOnes`).push({
lovedOne,
})
);
export const removeLovedOne = (userId, lovedOneKey) => (
db.ref(`users/${userId}/lovedOnes/${lovedOneKey}/lovedOne`).remove()
);
Here is what my schema looks like:
Any idea why I'm not able to add a loved one with the same name after removing it without refreshing the page?

Categories