forEach not a function when index starts at anything but 0 - javascript

When inside an object there isn't an index that starts with 0, it returns:
TypeError: data.forEach is not a function
This is how the database looks:
If I add an object with the index 0 to the database, like so (my formatting doesn't matter, this is just to illustrate the hierarchy):
0: {
email: "testmail",
uid: "testuid"
}
Suddenly the forEach function works and also retrieves the users with index 3 and 4. How can I make the forEach loop start at index 3 for example? Or is there a different method that I should be using instead? My code:
useEffect(() => {
if(props.klasData.gebruikers !== undefined) {
var data = props.klasData.gebruikers;
data.forEach(function (user) {
if(!emails.hasOwnProperty(user.email)) {
addEmail(oldArray => [...oldArray, user.email]);
}
setPending(false)
})
}
}, []);
Edit
props.klasData.gebruikers returns all keys within the "gebruikers" object with their children.

It looks like your data is being interpreted as an array by the Firebase SDK, since it starts with sequential numeric keys. If you print the value of your gebruikers snapshot, you'll see that it's:
[null, null, null, {email: "test", uid: "test"}, {email: "wouter#...", uid: "..."}]
These null values are added by the Firebase SDK to turn the keys into a proper array.
If you want to keep the Firebase SDK from converting the data into an array, prefix the keys with a non-numeric character. For example:
"key2": {email: "test", uid: "test"},
"key3": {email: "wouter#...", uid: "..."}
In general, it is more idiomatic to use the UID of the users as the keys in a collection of users. That way, you won't need to query for the UID, and you're automatically guaranteed that each user/UID can only be present in the collection one.

I changed the database like you can see here, as Frank van Puffelen suggested. As Frank also predicted, the root of my problem was coming from the function I didn't post.
By transforming all the indexes of UIDs I was fetching from the database to sequential numeric keys, I managed to get the forEach function working. I did this using users = Object.values(users).filter((el) => {return el != null}). The full effect hook can be found below:
useEffect(() => {
var refCodes = firebase.database().ref("userdata/" + currentUser.uid + "/docentCodes").orderByKey();
refCodes.once("value").then(function (snapshotCodes) {
snapshotCodes.val().forEach(function (code) {
var refCodeData = firebase.database().ref("klassencodes/" + code).orderByKey();
refCodeData.once("value").then(function (snapshotCodeData) {
var users = snapshotCodeData.val().gebruikers;
users = Object.values(users).filter((el) => {return el != null})
if(snapshotCodeData.val() !== null) {
setUsercount(oldArray => [...oldArray, users.length]);
setKlasData(oldArray => [...oldArray, snapshotCodeData.val()]);
setUserdata(oldArray => [...oldArray, users]);
addCode(oldArray => [...oldArray, code])
}
setPending(false);
})
})
})
}, []);
In the function where this useEffect is used, I added const [userdata, setUserdata] = React.useState([]); to acommodate this new information stripped down from indexes of UIDs to indexes made of numeric keys. This userdata is exported to another function, which has the effect hook as stated in the original question. I changed this up to be:
useEffect(() => {
if(props.userData !== undefined) {
var data = props.userData;
if(data !== undefined) {
data.forEach(function (user) {
if(!emails.hasOwnProperty(user.email)) {
addEmail(oldArray => [...oldArray, user.email]);
addUID(oldArray => [...oldArray, Object.keys(props.klasData.gebruikers)]);
}
setPending(false)
})
}
}
}, []);
Summary
In retrospect, I should've gone with a seperate const for just the userdata (snapshotCodeData.val().gebruikers), seperate from the other data returned from the snapshot (snapshotCodeData.val()).
I hope this may help you. The golden line of code is users = Object.values(users).filter((el) => {return el != null}).

Related

Upsert an array of objects using Supabase and Reactjs

I am new to the SQL scene and I am trying to use Supabase for a web app that I am building.
I am trying to push an array objects to the DB, but what I am finding is that it only grabs the first element in the array and adds that to the DB. The end goal functionality is that it grabs the whole array of objects and pushes that and if it detects a change (why I am using upsert) then add that object to the array.
ownedNFTs is an array of objects.
const nakedDB = () => {
try {
setLoading(true);
ownedNFTs.forEach(async (nft) => {
let { data, error, status } = await supabase.from("Users").upsert({
wallet_address: wallet?.accounts[0].address,
NFTS: {
name: nft?.metadata?.name || "",
description: nft?.metadata?.description || "",
image: nft?.metadata?.image || "",
externalUrl: nft?.metadata?.external_url || "",
contractAddress: nft?.contract.address || "",
tokenId: nft?.id.tokenId || "",
blockchain: "Ethereum",
metaverse: nft?.metadata?.name.split(" ")[0] || "",
metadata: nft?.metadata || "",
symbol: nft?.contract.symbol || "",
},
}).eq('wallet_address', wallet?.accounts[0].address);
console.log(data)
if (error && status !== 406) {
throw error;
}
});
} catch (error) {
console.table(error);
} finally {
setLoading(false);
}
};
I have tried following the Supabase documentation on bulkupserting, but that is to create multiple rows at once. I have also tried creating a standard for loop, but that didnt work as well. My current implementation is using forEach() and that is not working as expected.
The problem why it was selecting the first element (I believe) is because the .foreach() function was not running multiple times. This was not a clean way to do a DB call in any case. In addition to that I do think that the data I was sending to the DB was not in the correct format as a pure array of objects. For example when you use .map() it returns two separate objects. Supabase (in my configuration) was not expecting this format.
The solution is below.
I created an empty array
I used .map() on my array of objects
Saved the result of the .map() to the array I created.
Sent the new array to the DB in one call.
const nakedDB = async () => {
try {
ownedNFTs.map((nft) => {
// console.log(nft)
nftArray.push(nft);
});
setLoading(true);
let { data, error, status } = await supabase
.from("Users")
.upsert({
wallet_address: wallet?.accounts[0].address,
NFTS: nftArray,
})
.eq("wallet_address", wallet?.accounts[0].address);
if (error && status !== 406) {
throw error;
}
} catch (error) {
console.table(error);
} finally {
setLoading(false);
}
};
** Updated Solution **
ownedNFTs is already an array of objects in the proper format. I removed the .map() and directly saved ownedNFTs to the DB and it saves all elements. The .map() solution was me over-engineering the issue.

fetch gives me a full array but it is empty when i try to access it

I get an object from a Symfony rest API. This object has a property "shooting" which is an array and this array is full when I console.log it but when i try to access it, it is empty
This is my fetch request
const getProjectsAvailable = async () => {
const data = await fetch(
`${process.env.GATSBY_CORE_URI}/api/dashboard/supplier/projects/available`,
{
headers: {
[`X-Auth-Token`]: `${token}`,
[`Accept`]: `application/json`,
},
}
);
return data;
};
Here is the project object that i get back from fetch request
0: {id: 258, name: "Project26-1", reference: "A6568", isOfferValidated: null, source: "dashboard", …}
It has a shooting key which contains an array and it is not empty
shootings: Array(1)
0:
addressCity: "Paris"
addressCountry: {id: 76}
But when i set this object to my component state, all values stay the same except the shooting key which becomes an empty array
const [projects, setProjects] = useState([]);
useEffect(() => {
getProjectsAvailable().then(res =>
res.json().then(data => {
setProjects(data);
})
);
}, []);
I have no idea why does it act like that.
Thanks in advance
EDIT :
For example, the first line with console.log gives me the response object with a full shooting array while the second one sets it to my state but shooting array is empty
useEffect(() => {
getProjectsAvailable().then(response => console.log(response));
getProjectsAvailable().then(response => setProjects(response));
}, []);
Ok it is my bad. Somewhere else in the code, there was a .split() on the shooting property which mutates the array so the props changed and shooting array got empty

How to create multiple copies of a deeply nested immutable Object?

I'm trying to get data from firebase/firestore using javascript so i made a function where i get my products collection and passing this data to reactjs state.products by setState() method
My goal is to pass these products to my react state but also keeping the original data and not changing it by manipulating it. I understand that in JavaScript whenever we are assigning the objects to a variable we are actually passing them as a reference and not copying them, so that' why i used the 3 dots (spread syntax) to copy firestore data into tempProducts[] same way to copy it in virgins[]
getData = () => {
let tempProducts = [];
let virgins = [];
db.collection("products")
.get()
.then(querySnapshot => {
querySnapshot.forEach(item => {
const singleItem = { ...item.data() };
virgins = [...virgins, singleItem];
tempProducts = [...tempProducts, singleItem];
});
tempProducts[0].inCart = true;
this.setState(
() => {
return { products: tempProducts };
},
() => {
console.log(
"this.state.products[0].inCart: " + this.state.products[0].inCart
);
console.log("tempProducts[0].inCart: " + tempProducts[0].inCart);
console.log("virgins[0].inCart: " + virgins[0].inCart);
}
);
});
};
then calling this method in componentDidMount
componentDidMount() {
this.getData();
}
So I changed the first product inCart value in tempProducts to true when I console log tempProducts value and state.products value it gives me true all fine but I'm expecting to get false when i console log virgins value but i did not. I should also mention that all inCart values are false in firestore data.
I solved the issue by passing the original firestore data to virgins[] instead of the singleItem as it is an object referenced by tempProducts[] like so virgins = [...virgins, item.data()]; also it works if i copied the singleItem object instead of referencing it like so virgins = [...virgins, { ...singleItem }]; keep in mind that i have no idea if this solutions are in fact efficient (not "memory waste") or not.

React, Firebase: Access values of JSON and also get key value

I am beginner working with firebase, react. I am able to get the required data from firebase based on userEmail. But I am very confused in accessing the data.
firebase.database().ref('/users').orderByChild('email').equalTo(userEmail).on('value', data => {
console.log('data: ', data);
})
I get the following output:
data: Object {
"-Lhdfgkjd6fn3AA-": Object {
"email": "t5#gmail.com",
"favQuote": "this is it",
"firstName": "t5",
"lastName": "l5",
},
}
Please help me how to access all values ("-Lhdfgkjd6fn3AA-" , firstname, lastname, email and favQuote) into variables like: data.firstName, data.lastName, data.key, etc . Thank you.
let data = {
"-Lhdfgkjd6fn3AA-": {
"email": "t5#gmail.com",
"favQuote": "this is it",
"firstName": "t5",
"lastName": "l5",
},
};
console.log(Object.keys(data))//returning an array of keys, in this case ["-Lhdfgkjd6fn3AA-"]
console.log(Object.keys(data)[0])
console.log(Object.values(data))//returning an array of values of property
console.log(Object.values(data)[0].email)
Do need to be careful that the above code with the hardcoded "0" as index because it assumed that your data object has only one key. If you have more key, you can't simply replace index either because property of object has no predictable sequence
It's really a JavaScript question. I had to figure this out too. ...this works.
var p;
var thisLine;
p = docu.data();
for (var k in p) {
if (p.hasOwnProperty(k)) {
if (isObject(p[k])) {
thisLine = p[k];
Object.keys(thisLine).forEach(function (key, index) {
console.log(key, index);
});
}
}
}
function isObject(obj) {
return obj === Object(obj);
}
When you execute a query against the Firebase Database, there will potentially be multiple results. So the snapshot contains a list of those results. Even if there is only a single result, the snapshot will contain a list of one result.
So your first step is that you need to loop over the snapshot in your on() callback.
The second step is that you need to call Snapshot.val() to get the JSON data from the snapshot. From there you can get the individual properties.
firebase.database().ref('/users').orderByChild('email').equalTo(userEmail).on('value', snapshot => {
snapshot.forEach(userSnapshot => {
let data = userSnapshot.val();
console.log('data: ', data);
console.log(data.email, data.firstname);
});
})

How to set only ONE element in an array in firebase

I have an array saved on my firebase, like this:
matches:[ {match:{id:1,data:...}}]
I want to save just one item on firebase at this array. For example, my match have id:32. I want to find it on the array saved in firebase, and change it.
Im trying to make this. But Im thinking that this is VERY UGLY to make a request to the firebase, copy the array, and save the entire array again.
const ref = `/users/${currentUser.uid}/matches`;
var list = [];
firebase.database().ref(ref).on('value', function (snap) { list = snap.val(); });
if (list.length > 0) {
const indexToUpdate = list.findIndex(k => k.name == match.name)
if (indexToUpdate >= 0) {
list[indexToUpdate] = match;
return dispatch => {
firebase
.database()
.ref(`/users/${currentUser.uid}/matches`)
.set(list)
.then(() => {
dispatch({ type: MATCH_UPDATE, payload: match });
});
};
}
}
Any light?
This line of code:
const indexToUpdate = list.findIndex(k => k.name == match.name)
Is a dead giveaway that your data structure is not ideal.
Consider storing the matches under their name, prefixing it with a string to prevent the array coercion that Kenneth mentioned. So:
matches
match1
date: "2018-06-14..."
...
match2
date: "2018-06-16..."
...
With this you can look up the node for a match without needing a query. It also prevents using an array, which is an anti-pattern in Firebase precisely because of the reason you mention.
For more on this, see the classic Firebase blog post on Best Practices: Arrays in Firebase.
Firebase stores arrays as object and converts it back to array when it comes back to the client if the keys are ordered numerically correctly
But basically it should be able to work the same where you make your path up to the object you want to update.
firebase
.database()
.ref(`/users/${currentUser.uid}/matches/${indexToUpdate}`)
.set(match)
.then(() => {
dispatch({ type: MATCH_UPDATE, payload: match });
});
You need to reference the ID of the item you are trying to set by ID in the URL
firebase
.database()
.ref(`/users/${currentUser.uid}/matches/32`)
.set(list)
.then(() => {
dispatch({ type: MATCH_UPDATE, payload: match });
});
Also, use snap.key to get the ID you need if index isn't working.

Categories

Resources