Why getting too many rerenders in react? - javascript

I have the following code in React to get data from Firebase. I am new to useEffect and it is giving too many rerenders error:
let post1 = [];
useEffect(() => {
getDocs(collection(db, "posts")).then((snapshot) => {
const data = snapshot.docs.map((doc) => ({
id: doc.id,
...doc.data(),
}));
post1 = data;
})
}, [posts]);
setPosts(post1);

The way you wrote it, setPosts(post1) is being called on every render. I'm assuming posts and setPosts are destructured from a useState() value which means that every time you call setPosts(), it triggers a rerender. You need to move the setPosts() call to the useEffect(). You also need to remove posts from the dependency array of useEffect because if any of those dependencies change, it triggers a rerender as well. In your specific case, try this:
useEffect(() => {
getDocs(collection(db, "posts")).then((snapshot) => {
const data = snapshot.docs.map((doc) => ({
id: doc.id,
...doc.data(),
}));
setPosts(data);
})
}, []);

Related

How to Store and Update then again store in useState

I'm using here useState to store data, update current value, by using this it's not store previous data, it's only store update current value which do useState. But I want to store previous data and update data all of them, somethings like, i have 10 api's data and i want to call them end of my api using ids.
i want to store 10 api's data in one array.
how to store 10 apis data in together using useState();
my code is somethings like:
const [dataLoc, setDataLoc] = useState([]);
const ids = [1,2,3,4,5,6,7,8,9,10]
useEffect(() => {
ids?.map((id) => {
fetch(`https://jsonplaceholder.typicode.com/posts/${id}`)
.then((response) => response.json())
.then((dataLoc) => setDataLoc(dataLoc.title))
.catch((error) => console.error(error))
})
}, []);
console.log(dataLoc);
output is:
enter image description here
but it's in 10 array the out put but i want all the title in one array.
anyone can help me how can i do this here?
ThankYou for Your Trying in advance!
You can make use of react states prev-callback like that:
const [dataLoc, setDataLoc] = useState([]);
useEffect(() => {
ids?.map((id) => {
fetch(`https://jsonplaceholder.typicode.com/posts/${id}`)
.then((response) => response.json())
.then((dataLoc) => setDataLoc((prev) => [...prev, dataLoc.title]))
.catch((error) => console.error(error));
});
}, []);
console.log(dataLoc);
By spreading ...prev and appending it with , dataLoc.title you add each entry to the previous state value. The order is the one in which the calls go through, since we insert the new element at the end.
Edit
If you want to console.log multiple times you can make use of another useEffect inside your component:
useEffect(() => {
console.log(dataLoc);
}, [dataLoc]);
Issue
The code is enqueueing state updates in a loop and not updating from the previous state. In other words, each enqueued update in the loop overwrites the previous update. The update from the last loop iteration is the one the app sees on the subsequent render.
Solution
Either use a functional state update to correctly enqueue state updates and update from the previous state. Use .forEach instead of .map.
const [dataLoc, setDataLoc] = useState([]);
useEffect(() => {
ids?.forEach((id) => {
fetch(`https://jsonplaceholder.typicode.com/posts/${id}`)
.then((response) => response.json())
.then((dataLoc) => setDataLoc(dataLoc => [...dataLoc, dataLoc.title]))
.catch((error) => console.error(error))
})
}, []);
Or map the ids to an array of Promises and Promise.all them and enqueue a single state update.
const [dataLoc, setDataLoc] = useState([]);
useEffect(() => {
const requests = ids?.map((id) => {
return fetch(`https://jsonplaceholder.typicode.com/posts/${id}`)
.then((response) => response.json())
.then((dataLoc) => return dataLoc.title)
.catch((error) => console.error(error))
});
Promise.all(requests)
.then(ids => {
setDataLoc(ids)
});
}, []);
In your case it is overriding the previous values. You can merge the values
React.useEffect(() => {
ids?.map((id) => {
fetch(`https://jsonplaceholder.typicode.com/posts/${id}`)
.then((response) => response.json())
.then((dataLoc) =>
setDataLoc((prevState) => {
return [...prevState, dataLoc.title];
})
)
.catch((error) => console.error(error));
});
}, []);
you can use push method to add each title to the Array:
const [dataLoc, setDataLoc] = useState([]);
const ids = [1,2,3,4,5,6,7,8,9,10]
useEffect(() => {
ids?.map((id) => {
fetch(`https://jsonplaceholder.typicode.com/posts/${id}`)
.then((response) => response.json())
.then((dataLoc) => setDataLoc(dataLoc.push(dataLoc.title)))
.catch((error) => console.error(error))
})
}, []);
console.log(dataLoc);

Reading firestore sub-collection data in react - how to set a parent id in the sub-collection where query

I'm trying to figure out how to read firestore sub collection data from react.
I have seen this blog that describes how to do it and am now trying to figure out how to implement the logic of it. I have also seen this gist which sets out a way to assign a parent id to a subcollection where query.
I have also been trying to understand this blog post, which I think is very close to what I am trying to do (it looks for the user collection id to populate the list of items subcollection data to render). I think the key to solving this problem is figuring out how to make a version of this function, but instead of monitoring a url parameter, I'm trying to use the particular glossaryTerm.id in the hook that looks for that glossary document's relatedTerms:
I have a parent collection called glossary and a sub collection within it called relatedTerms.
I have a form that works to correctly submit data to the glossary (parent) and relatedTerms (sub) collections.
I'm struggling to find a way to use react hooks to generate the list of glossary terms (this collection holds data) with associated related terms sub collection data).
My current attempt at doing that is:
function useGlossaryTerms() {
const [glossaryTerms, setGlossaryTerms] = useState([])
// First useEffect - to set the glossaryTerm (the parent document)
useEffect(() => {
async function fetchGlossaryData() {
await firebase
.firestore()
.collection("glossary")
.orderBy('term')
.onSnapshot(snapshot => {
const glossaryTerms = snapshot.docs.map(doc => ({
id: doc.id,
...doc.data(),
}))
setGlossaryTerms(glossaryTerms)
});
fetchGlossaryData();
};
// setRelatedGlossaryTerms(glossaryTerms)
}, [])
return glossaryTerms;
};
function useRelatedGlossaryTerms() {
const [relatedGlossaryTerms, setRelatedGlossaryTerms] = useState([])
const glossaryTerms = useGlossaryTerms()
console.log("try logging from inside relatedTerms function", glossaryTerms)
// Second useEffect - to set the relatedGlossaryTerm (the sub collection document)
useEffect(() => {
async function fetchRelatedGlossaryData() {
await firebase
.firestore()
.collection("glossary")
.doc([I'm trying to find a way to populate this field with the id of the glossary doc used in the map])
//.doc("JsSQ3Qf0A65ixNbF07G2") works to render the placeholder text but without the subcollection document data.
//.doc("glossary.id") gets ignored
// {console.log("testing:", glossaryTerms)}
.collection('relatedTerms')
//.where("glossaryId", "==", "glossaryTerms.id")
**** This is wrong, but I can't find a way that works. I think I'm trying to ask for the state of glossaryTerms and then the id on the
relevant object" ****
// .where("glossaryId", "==", "JsSQ3Qf0A65ixNbF07G2"
**** This is also wrong, but I can't understand why. If the above attempt is not possible because glossaryTerms is an object, then that
element of this problem should be solved by using the string stored on
the subcollection document. It doesn't work. It doesn't generate any
console errors and I still can't find a way to log the value of the
subcollection." ****
.onSnapshot(snapshot => {
const relatedGlossaryTerms = snapshot.docs.map(doc => ({
id: doc.id,
...doc.data(),
}))
setRelatedGlossaryTerms(relatedGlossaryTerms)
});
fetchRelatedGlossaryData();
}
// setRelatedGlossaryTerms(glossaryTerms)
}, [glossaryTerms])
return relatedGlossaryTerms;
};
const GlossaryTerms = () => {
const glossaryTerms = useGlossaryTerms()
const relatedGlossaryTerms = useRelatedGlossaryTerms()
const classes = useStyles();
return (
<div className={classes.root}>
{glossaryTerms.map(glossaryTerm => {
{console.log('testing log of glossaryTerms', glossaryTerms)}
return (
<div key={glossaryTerm.id}>
<Typography className={classes.heading}>{glossaryTerm.term}</Typography>
<div>
{glossaryTerm.category.map(category => (
<Typography className={classes.secondaryHeading}>
{category.label}
</Typography>
)
)}
<div>
<Typography variant="subtitle2" className={classes.heading2}>Meaning</Typography>
<Typography>{glossaryTerm.definition}</Typography>
</div>
{console.log("testing log of glossaryTerm", glossaryTerm)}
this works to log the single glossaryTerm in the map.
{console.log("testing log of relatedGlossaryTerm", relatedGlossaryTerms)}
this returns an empty array. This doesn't make any sense to me. The glossaryTerm with the id returned in the log above has a subcollection
(called relatedTerms) with data that should be used to populate the
relatedGlossaryTerms state.
<div>
{relatedGlossaryTerms ? (
<ul>
{relatedGlossaryTerms.map(relatedGlossaryTerm => (
<div key={relatedGlossaryTerm.id}>
<Typography variant="caption">
Placeholder title {relatedGlossaryTerm.title} |
Placeholder description {relatedGlossaryTerm.description}
</Typography>
</div>
))}
</ul>
) : null}
{
glossaryTerm.templates ? (
<div>
<p><Typography variant="caption" >Related Templates</Typography></p>
{glossaryTerm.templates.map(template => (
<Link to="" className="bodylinks" key={template.id}>
<p><Typography variant="caption" >{template.title}
</Typography></p>
</Link>
))}
</div>
) : null
}
</div>
<div>
)
})}
</div>
</div>
</div>
);
}
export default GlossaryTerms;
When I try this, I don't get any errors in the console, but, the rendered output is incorrect and confusing.
The glossary currently has 2 documents in that collection. One of the documents has a relatedTerms subcollection which also has 2 documents. The other has no sub collection entries (other than a glossaryId field which I add to the setSubmitting form add so that I could try matching the string ids to filter sub collection documents - this attempt is outlined in the history of this post and does not work. I think it may need group collection queries - but I don't think this approach should be necessary to access a single subcollection from a parent id).
Whilst the glossary data is correctly rendered, the sub collection data for relatedTerms is not rendered at all - BUT the placeholder text is repeated for each instance of the parent collection (2 documents). I currently have 2. If I add another entry to the sub collection to test this, the 2 placeholder sets of text are repeated in each glossary document rendered.
The image below shows the rendered output.
The image below shows the content of the glossary collection.
The image below shows the content of the relatedTerms sub collection.
This is confusing, because the placeholder text is repeated the same number of times as is equal to the number of parent collection document entries. It's not testing for sub collection values.
It's also incorrect because I can't find a way to join the relatedTerm to its relevant glossaryTerm collection entry. Each item in the list should render its sub collection content only.
When I try logging the relatedGlossaryTerms, as follows:
{console.log("testing log of relatedGlossaryTerms", relatedGlossaryTerms)}
{relatedGlossaryTerms ? (
<ul>
{relatedGlossaryTerms.map(relatedGlossaryTerm => (
I get:
This log is confusing to me because it's supposed to be the content of the sub collection. Instead it logs the parent collection.
I can't find a way to log the value of the relatedGlossaryTerms.
When I try adding console log statements to the snapshot, I get errors that I can't understand. Plenty of other posts show this as a correct method to use to log the values. I tried adding curly braces, but get the same issue.
useEffect(() => {
firebase
.firestore()
.collection("glossary")
.doc()
// {console.log("testing:", glossaryTerms)}
.collection('relatedTerms')
// .where("glossaryId" === "SQ3Qf0A65ixNbF07G2")
.onSnapshot(snapshot => {
const relatedGlossaryTerms = snapshot.docs.map(doc => ({
id: doc.id,
...doc.data(),
// console.log('test useEffect setting for relatedGlossaryTerms', doc),
// console.log(relatedGlossaryTerms)
}))
setRelatedGlossaryTerms(relatedGlossaryTerms)
});
When I try logging the values of glossaryTerms and relatedGlossaryTerms in the return statement, I get:
Trying Yoruba's (now deleted) suggestion
I tried Yoruba's suggestion to use get instead of snapshot. Although it does not make sense to me as to why I can't use snapshot without mobx, I tried it.
useEffect(() => {
firebase
.firestore()
.collection("glossary")
.orderBy('term')
.get()
.then(snapshot => {
const glossaryTerms = snapshot.docs.map(doc => ({
id: doc.id,
...doc.data(),
}))
setGlossaryTerms(glossaryTerms)
});
// setRelatedGlossaryTerms(glossaryTerms)
}, [])
return glossaryTerms;
useEffect(() => {
firebase
.firestore()
.collection("glossary")
.doc()
// {console.log("testing:", glossaryTerms)}
.collection('relatedTerms')
// .where("glossaryId" === "SQ3Qf0A65ixNbF07G2")
.get()
.then(snapshot => {
const relatedGlossaryTerms = snapshot.docs.map(doc => ({
id: doc.id,
...doc.data(),
}))
setRelatedGlossaryTerms(relatedGlossaryTerms)
});
// setRelatedGlossaryTerms(glossaryTerms)
}, [glossaryTerms])
return relatedGlossaryTerms;
};
It doesn't make any difference. No new console errors. No difference to the rendering problems outlined above.
ALL PREVIOUS ATTEMPTS
onSubmit={(values, { setSubmitting }) => {
setSubmitting(true);
// firestore.collection("glossary").doc().set({
// ...values,
// createdAt: firebase.firestore.FieldValue.serverTimestamp()
// })
// .then(() => {
// setSubmitionCompleted(true);
// });
// }}
const newDocRef = firestore.collection("glossary").doc() // auto generated doc id saved here
let writeBatch = firestore.batch();
{console.log("logging values:", values)};
writeBatch.set(newDocRef,{
term: values.term,
definition: values.definition,
category: values.category,
context: values.context,
createdAt: firebase.firestore.FieldValue.serverTimestamp()
});
writeBatch.set(newDocRef.collection('relatedTerms').doc(),{
// dataType: values.dataType,
// title: values.title,
glossaryId: newDocRef.id,
...values.relatedTerms
// description: values.description,
})
writeBatch.commit()
.then(() => {
setSubmitionCompleted(true);
});
}}
No error message is shared when I try this, the glossaryId just gets ignored. It remains a mystery to me what the batch concept is- I thought it only ran if all the instructions could be performed.
I'm trying to figure out how to access the data stored in the sub collection. My current attempt at doing that is based on the example in the post linked above.
import React, { useState, useEffect } from 'react';
import {Link } from 'react-router-dom';
import Typography from '#material-ui/core/Typography';
import firebase, { firestore } from "../../../../firebase.js";
import { makeStyles } from '#material-ui/core/styles';
import clsx from 'clsx';
const useStyles = makeStyles((theme) => ({
root: {
width: '100%',
marginTop: '8vh',
marginBottom: '5vh'
},
}));
function useGlossaryTerms() {
const [glossaryTerms, setGlossaryTerms] = useState([])
const [relatedGlossaryTerms, setRelatedGlossaryTerms] = useState([])
useEffect(() => {
firebase
.firestore()
.collection("glossary")
.orderBy('term')
.onSnapshot(snapshot => {
const glossaryTerms = snapshot.docs.map(doc => ({
id: doc.id,
...doc.data(),
}))
setGlossaryTerms(glossaryTerms)
});
// setRelatedGlossaryTerms(glossaryTerms)
}, [])
return glossaryTerms;
}
const relatedTermsList = (glossaryTerm) => {
setGlossaryTerms(glossaryTerm);
firebase.firestore().collection('glossary').doc(glossaryTerm.id).collection('relatedTerms').get()
.then(response => {
const relatedGlossaryTerms = [];
response.forEach(document => {
const relatedGlossaryTerm = {
id: document.id,
...document.data()
};
relatedGlossaryTerms.push(relatedGlossaryTerm);
});
setRelatedGlossaryTerms(relatedGlossaryTerms);
})
.catch(error => {
// setError(error);
});
}
const GlossaryTerms = () => {
const glossaryTerms = useGlossaryTerms()
const relatedGlossaryTerms = useGlossaryTerms()
const classes = useStyles();
return (
<div>
{glossaryTerms.map(glossaryTerm => {
return (
{glossaryTerm.term}
{glossaryTerm.category.map(category => (
{category.label}
)
)}
</div>
{glossaryTerm.definition}
{glossaryTerm ? (
<ul>
{relatedGlossaryTerms.map(relatedGlossaryTerm => (
<li key={relatedGlossaryTerm.id}>
{relatedGlossaryTerm.title} | {relatedGlossaryTerm.description}
</li>
))}
</ul>
) : null}
)
})}
</div>
);
}
export default GlossaryTerms;
When I try this, I get error messages saying that in my relatedTermsList const, the definitions of setGlossaryTerms and setRelatedGlossaryTerms are undefined.
Each of these errors are odd to me because setRelatedGlossaryTerms is defined in the same way as setGlossaryTerms. I don't understand why it is unrecognisable and setGlossaryTerms is used in the useEffect without any issue.
NEXT ATTEMPT
I tried using a second useEffect function that takes a glossaryTerm.
function useGlossaryTerms() {
const [glossaryTerms, setGlossaryTerms] = useState([])
const [relatedGlossaryTerms, setRelatedGlossaryTerms] = useState([])
useEffect(() => {
firebase
.firestore()
.collection("glossary")
.orderBy('term')
.onSnapshot(snapshot => {
const glossaryTerms = snapshot.docs.map(doc => ({
id: doc.id,
...doc.data(),
}))
setGlossaryTerms(glossaryTerms)
});
// setRelatedGlossaryTerms(glossaryTerms)
}, [])
return glossaryTerms;
}
useEffect((glossaryTerm) => {
firebase
.firestore()
.collection("glossary")
.doc(glossaryTerm.id)
.collection('relatedTerms')
.onSnapshot(snapshot => {
const relatedGlossaryTerms = snapshot.docs.map(doc => ({
id: doc.id,
...doc.data(),
}))
setRelatedGlossaryTerms(relatedGlossaryTerms)
})
.catch(error => {
// setError(error);
});
});
const GlossaryTerms = () => {
const glossaryTerms = useGlossaryTerms()
const relatedGlossaryTerms = useGlossaryTerms()
const classes = useStyles();
return (
I don't understand why, but the error is that setRelatedGlossaryTerms is not defined - where it's used in the second useEffect.
NEXT ATTEMPT
This attempt does not produce any errors in the console but the result is incorrect.
I have two useEffect functions as follows:
function useGlossaryTerms() {
const [glossaryTerms, setGlossaryTerms] = useState([])
const [relatedGlossaryTerms, setRelatedGlossaryTerms] = useState([])
useEffect(() => {
firebase
.firestore()
.collection("glossary")
.orderBy('term')
.onSnapshot(snapshot => {
const glossaryTerms = snapshot.docs.map(doc => ({
id: doc.id,
...doc.data(),
}))
setGlossaryTerms(glossaryTerms)
});
// setRelatedGlossaryTerms(glossaryTerms)
}, [])
return glossaryTerms;
useEffect((glossaryTerms) => {
firebase
.firestore()
.collection("glossary")
.doc("glossaryTerms.id") //I also tried .doc(glossaryTerms.id)
.collection('relatedTerms')
.onSnapshot(snapshot => {
const relatedGlossaryTerms = snapshot.docs.map(doc => ({
id: doc.id,
...doc.data(),
}))
setRelatedGlossaryTerms(relatedGlossaryTerms)
});
// setRelatedGlossaryTerms(glossaryTerms)
}, [])
return relatedGlossaryTerms;
};
const GlossaryTerms = () => {
const glossaryTerms = useGlossaryTerms()
const relatedGlossaryTerms = useGlossaryTerms()
const classes = useStyles();
return (
The intention behind these two useEffects is that the first one finds the glossaryTerm and then the second one uses the glossaryTerm.id to find if there are any related terms and then sets the state of the relatedGlossaryTerms array with any related terms.
Then in the render, I have:
{glossaryTerm.category.map(category => (
<Typography className={classes.secondaryHeading}>
{category.label}
</Typography>
)
The above works to correctly find the glossaryTerm.
{relatedGlossaryTerms ? (
<ul>
{relatedGlossaryTerms.map(relatedGlossaryTerm => (
<div>
<Typography variant="caption">
Placeholder title {relatedGlossaryTerm.title} |
Placeholder description {relatedGlossaryTerm.description}
</Typography>
</div>
))}
</ul>
) : null}
The intention is for the above to look to see if there are any relatedGlossaryTerms (which should be the result of finding the sub collection inside the glossaryTerm.
This doesn't produce any console errors, but it does 2 things that need to be corrected.
it renders the same result for each glossaryTerm (regardless of whether it actually has any relatedGlossaryTerms.
It does not render the relatedGlossaryTerm data. The picture below shows the placeholder text printed, repeatedly (as many times as is equal to the total number of related terms in all glossaryTerms documents combined).
I'm trying to find a way to use the glossaryTerm document id to find the sub collection data and render it only inside the relevant glossaryTerm item.
Next Attempt
I also tried:
useEffect((glossaryTermsId) => {
firebase
.firestore()
.collection("glossary")
.doc(glossaryTermsId)
.collection('relatedTerms')
.where(glossaryTermsId === "glossaryId") // I also tried .where(glossaryTerms.id === "glossaryId)
.onSnapshot(snapshot => {
const relatedGlossaryTerms = snapshot.docs.map(doc => ({
id: doc.id,
...doc.data(),
}))
setRelatedGlossaryTerms(relatedGlossaryTerms)
});
I have added a field to the sub collection called glossaryId to try to use the where query to filter the sub collection (although even if this worked, which it doesn't) - I don't want to search every glossaryTerm document to get the right relatedTerms.
When I try
useEffect((glossaryTerms) => {
firebase
.firestore()
.collection("glossary")
.doc(glossaryTerms)
.collection('relatedTerms')
.where("glossaryId" === "JsSQ3Qf0A65ixNbF07G2")
.onSnapshot(snapshot => {
const relatedGlossaryTerms = snapshot.docs.map(doc => ({
id: doc.id,
...doc.data(),
}))
setRelatedGlossaryTerms(relatedGlossaryTerms)
});
I still get the same result as shown in the picture above (just the placeholder text prints - the same way in all of the glossaryTerms - it just ignores the query parameter of the glossaryId attribute in the sub collection document.
NEXT ATTEMPT
I'm scratching for ideas for things to try now. I've tried adding glossaryTerms as a dependency to the second useEffect and I tried giving the glossaryTermsId as a prop (instead of the glossaryTerms object), as follows:
useEffect((glossaryTermsId) => {
firebase
.firestore()
.collection("glossary")
.doc(glossaryTermsId)
.collection('relatedTerms')
// .where("glossaryId" === "JsSQ3Qf0A65ixNbF07G2")
.onSnapshot(snapshot => {
const relatedGlossaryTerms = snapshot.docs.map(doc => ({
id: doc.id,
...doc.data(),
}))
setRelatedGlossaryTerms(relatedGlossaryTerms)
});
// setRelatedGlossaryTerms(glossaryTerms)
}, [glossaryTerms])
return relatedGlossaryTerms;
};
Neither of these things make any difference to the outcomes shown above.
When I try to log the content of glossaryTerm in the render - one strange thing is that the sub collection data is not displayed. I only have 2 test entries in the glossary collection. 1 has a related term and the other doesn't. Neither is visible in the console log. I think this is because of shallow queries. But, I can't then find a way to link the collection query to the sub collection data.
Next attempt
Taking Bennett's advice, I removed the parameters from the useEffect as follows:
useEffect(() => {
firebase
.firestore()
.collection("glossary")
.doc()
// {console.log("testing:", glossaryTerms)}
.collection('relatedTerms')
// .where("glossaryId" === "JSQ3Qf0A65ixNbF07G2")
.onSnapshot(snapshot => {
const relatedGlossaryTerms = snapshot.docs.map(doc => ({
id: doc.id,
...doc.data(),
}))
setRelatedGlossaryTerms(relatedGlossaryTerms)
});
// setRelatedGlossaryTerms(glossaryTerms)
}, [glossaryTerms])
return relatedGlossaryTerms;
};
When I try this, it still renders the same output as shown in the image above.
When I uncomment the where query (which is the doc id of a document which has a sub collection for relatedTerms, I'm expecting to be able to get to the subcolleciton data (even if its in all the glossaryTerms, instead of just the one to which the sub collection belongs).
Instead, I get the placeholder text, rendered the number of times as is equal to the total number of related terms recorded in all sub collections in all the glossaryTerms documents (but no data).
Then when I change the string, so it should evaluate to null, I get the same rendered output (still prints the placeholders).
I did try with real data like you db but you can't list subcollections of a document.
I think the best way is the create a separate collection for relatedTerms and during save to database you have the save the form data into 2 separate collections.
Or build you viewmodel at the backend with firebase functions and send this to the view.
https://firebase.google.com/docs/firestore/query-data/get-data#web_6
const collectionref = collection(fire, 'FirstLevel/' + doc + '/LastLevel');
onSnapshot(collectionref, (querysnapshot) => { console.log("Read Msg");
const msgstream = querysnapshot.docs.map(doc => ({ data: doc.data(), id: doc.id }));
console.log(msgstream) }, (error) => { console.log(error.message) });
replace FirstLevel with your main collection and concat with doc then add nested collection in LastLevel make sure to catch error is the event the nested collection doesnt exist.
check https://firebase.google.com/docs/firestore/query-data/listen
This works but the view gets loaded for every glossary child.
Also the database is hit for every glossary child.
That's why it is better to put relatedTerms into a separate collection.
Then you can get the glossary collection with 1 db call and the relatedTerms with 1 db call.
const [glossaryTerms, setGlossaryTerms] = useState([])
const [relatedGlossaryTerms, setRelatedGlossaryTerms] = useState([])
useEffect(() => {
// get the glossary collection
firebase.firestore()
.collection("glossary").orderBy('term').get().then(glossaries => {
let tempGlossaryTerms = []
let tempRelatedGlossaryTerms = []
glossaries.forEach(doc => {
const glossary = {id: doc.id, ...doc.data()}
tempGlossaryTerms.push(glossary)
// for each glossary get the related term
firebase.firestore()
.collection("glossary")
.doc(glossary.id)
.collection('relatedTerms')
.get().then(relatedTerms => {
relatedTerms.docs.forEach(doc => {
const relatedTerm = {id: doc.id, ...doc.data()}
tempRelatedGlossaryTerms.push(relatedTerm)
})
setRelatedGlossaryTerms(tempRelatedGlossaryTerms)
})
})
setGlossaryTerms(tempGlossaryTerms)
})
}, [])

How to set value of react hook in promise?

I am getting a data from the promise and I want to set it in react hook, it does but it makes infinite requests and re-rendering of the page, also I want some specific only data to fill
const [rows, setRows] = useState([]);
useEffect(() => {
async function fetchData() {
myEmploiList({
user: user.id
}).then((data) => {
//setRows(data);
const newData = [];
data.forEach((item, index) => {
newData[index] = {
id: item._id,
name: item.name,
society: item.society
};
setRows(newData);
});
});
}
fetchData();
});
You should add dependencies to your useEffect hook. It is the second argument of this hook.
useEffect(() => {
// your code
}, [deps]);
deps explanation:
no value: will execute effect every time your component renders.
[]: will execute effect only the first time the component renders.
[value1, value2, ...]: will execute effect if any value changes.
For further reading, I highly recommend this blog post.
Move setRows call out of the forEach loop and include user.id into the dependency array
const [rows, setRows] = useState([]);
useEffect(() => {
async function fetchData() {
myEmploiList({
user: user.id
}).then((data) => {
//setRows(data);
const newData = [];
data.forEach((item, index) => {
newData[index] = {
id: item._id,
name: item.name,
society: item.society
};
});
setRows(newData);
});
}
fetchData();
}, [user.id]);

React Context - Set state then reference it straight after?

I am working with a list of items and I want to set a selected item id, then fetch the selected item using the id.
I am updating the state of a context using reducers which works, but when referencing the property of the state, it doesn't see the updated value.
Component.js
useEffect(() => {
setSelectedId(5);
getSelectedItem();
}, []);
Context.js
const initialState = {
selectedId: 0,
};
const [state, dispatch] = useReducer(ItemReducer, initialState);
const setSelectedId = id => dispatch({ type: SET_SELECTED_ID, payload: id });
// HERE IT THINKS 'state.selectedId' IS 0
const getSelectedItem = async () => {
const selectedItem = await fetch(url + state.selectedId);
};
The Chrome dev tools show the selected id property is updated to 5, but the getSelectedItem function sees 'selectedId' as 0 not 5.
This could be a simple issue and any help would be appreciated.
Thanks
You will need to resolve the promise that would be returned. Something like:
useEffect(() => {
setSelected(5).then(getSelectedItem());
}, []);
You could move getSelectedItem(); into its own useEffect that's dependent on the selectedItemId, that way it will only perform getSelectedItem once the value of selectedId is set.
useEffect(() => {
if(selectedId) getSelectedItem();
}, [selectedId]);

How to orderByValue onSnapshot using Firestore

In componentWillMount I am registering an onSnapshot function which updates the state.
componentWillMount () {
todoRef.onSnapshot((doc) => {
let todos = []
doc.forEach(doc => {todos.push(doc.data())})
this.setState({
todos
})
})
}
However, the way that firebase/firestore works is that it just pushes up random keys, so when I get the data back it's not in the correct order.
I know there is a .orderByValue() function but I've tried implementing it and can't seem to figure it out.
My ref was const todoRef = db.collection("todos");
So once you've got the reference to your collection, you can then do the sort query.
todoRef.orderBy('createdAt').onSnapshot((docSnapShot) => {
let todos = []
docSnapShot.forEach(doc => {todos.push(doc.data())})
this.setState({
todos
})
})
const [todos, setTodos] = useState([]);
useEffect(() => {
db.collection('todo_collection')
.orderBy('dateTime')
.onSnapshot((snapshots) => {
setTodos(snapshots.docs.map((doc) => doc.data()));
});
}, []);
For Forebase +9.0.0, you need to create a query and provide the orderBy as an argument:
we import the functions we need
import {collection, orderBy,query, onSnapshot};
let's create the collection reference
const collRef = collection(db,"todos");
let's create the query now : the first argument is a reference to our collection, the second is orderBy(pass a field name: string)
const q = query (collRef, orderBy("createdAt"));
Now we can pass it to the onsnapshot fuction.
onSnphot(q,(snapshot)=>{
//things to do with the snapshot
let todos = []
snapshot.forEach(doc => {todos.push(doc.data())})
this.setState({
todos
})
})

Categories

Resources