Why Data disappear after refreshing React JS - Firebase? - reactjs

Hello there's something I really do not understand because it does not make any sense at all, I have 2 pieces of code that are EXACTLY the same only difference is the variables I use to find the collections and one does persist even after refreshing and the other one doesn't here are the pieces of code:
Students list one:
const [estudiantes, setEstudiantes] = useState([]);
const estudiantesRef = db.collection("usuarios").doc(user.uid).collection("estudiantes")
useEffect(() => {
estudiantesRef.onSnapshot(snapshot => {
const tempData = [];
snapshot.forEach((doc) => {
const data = doc.data();
tempData.push(data);
});
setEstudiantes(tempData);
})
}, []);
console.log(user.uid)
console.log(estudiantes)
Books list one:
const [productos, setProductos] = useState([]);
const productosRef = db.collection('libros');
useEffect(() => {
productosRef
.orderBy('grado')
.onSnapshot( snapshot => {
const tempData = [];
snapshot.forEach((doc) => {
const data = doc.data();
tempData.push(data);
});
setProductos(tempData);
})
}, []);
Gif student
Gif books list
Update: The user.uid is ALWAYS present regardless of the refresh however the data stored in estudiantes disappears when I refresh and that's not good lol. Why does the data disappear ? I test it by only loading 1 collection and it doesn't disappear so why does it disappear when it goes to multiple collections and how I can fix it ?
Before Refresh
After Refresh

The two pieces of code might seem to do exactly the same thing, but the reality is, they are not.
estudiantesRef references: collection > document > collection
productosRef references: collection
Naturally you'd expect productosRef to return faster as it's only doing one lookup, but it also depends on the number of records in each collection, etc. So you should double check this. Also, any additional processing done on the data after retrieval will affect the time it takes to show.
My suggestion is you create a loading state (or use console.log) to double check you're getting back data when you expect to.
one does persist even after refreshing and the other one doesn't
Unless you're using something other than React.useState to store your state, then the data shouldn't be persisting between refreshes. This could be a result of local caching, or the page just loading quickly.

Not sure if you still need an answer, but:
For data to be kept on refresh, you should set your items in localStorage
const item= localStorage.setItem('keyValue', 'dataToPersist')
then get data by:
const item= localStorage.getItem('keyValue')

I just want to clarify that after a week or more of fighting with this issue, the answer wasn't because it was a nested collection
(Nested)
estudiantesRef references: collection > document > collection
(not nested)
productosRef references: collection
it was because the user from my code was being set back to null as soon as you refresh but if you wait like 2 seconds it changes back to his normal value. all I had to do is use user in the array dependency in my firebase useEffect.
However this means that the issue wasn't because it was a nested collection as someone else pointed out. just that, firebase can load data instantly (as long as there aren't that many values clearly) there shouldn't be an issue in loading just 1 piece of information.

Related

How to get a Firestore id of stored item in React?

I'm storing some data on the Firestore, it's basically a collection of posts. So I need to get a Firestore collection id of the particular item.
I could do that like this :
const data = await getDocs(postsDataRef); //getting array of items
const id = (data.docs[1]._key.path.segments[6]); // choosing particular data
But maybe there is a special method for such operation? Thanks!
If you're getting all docs, you can print the IDs of all of them with:
data.forEach((doc) => {
console.log(doc.id);
})

Setting state before render with ref to firestore

I am working through a simple logical problem, but I cannot seem to have things work smoothly. Let me share my most convincing code experiment and then I'll share some thoughts.
useEffect(() => {
firebase
.firestore()
.collection("someCollection")
.orderBy("date", "desc")
.onSnapshot(docs => {
let documents = []
if (canGetUpdatesFromFirestore.current) {
docs.forEach((doc) => {
documents.push(doc.data())
})
if(documents.length > 3) {
documents.splice(4, 0, {questionPostId: 0})
documents.splice(5, 0, {questionPostId: 1})
}
setAllQuestions(documents)
setUsers(documents)
}
})
if (searchValue.length > 2) {
canGetUpdatesFromFirestore.current = false;
functions.searchForSearchVal(searchValue, "Sexuality")
.then((result) => {
setAllQuestions(result);
})
} else {
canGetUpdatesFromFirestore.current = true
}
}, [searchValue])
function setUsers(docs){
let arrFinal = []
let copyOfAllQuestions = ""
for(let i = 0; i< docs.length; i++) {
console.log("HERE")
if (docs[i].postedBy) {
docs[i].ref.get().then(userFire => {
copyOfAllQuestions = {
...allQuestions,
...{hasPremium: userFire.data().hasPremium}
}
})
arrFinal.push(copyOfAllQuestions)
}
}
setAllQuestions(arrFinal)
}
Let me share some of my current state and what I am trying to accomplish.
I have a that display allQuestions. Each question data has a ref to its user document in firestore. For each question I need to check if that user hasPremium. How should I go about doing that the correct way?
The problem currently is that I can get the data from my Users collection through the ref, but I have to refresh my state in order for it all to show.
Could someone help me get on the right path / think correctly on this one please?
One approach that I put forward is to embrace data denormalization. That is, rather than putting references to other documents (Users) inside of the Questions document, put all the relevant user information directly into the Questions document.
This is antithetical to SQL database approaches, but that's okay because Firestore is "NoSQL". Embrace the anti-SQL-idity!!
Essentially, in your Question document you want to copy in whatever information is required in your app when working with a Question, and avoid doing "joins" by fetching other documents. You don't need to copy in all of the User document into a Question document - just the elements needed when your app is working with a Question.
For example, maybe in the question all you need is:
question: {
name: ...,
type: ...,
lastUpdated: ...,
postedBy: {
email: ...,
displayName: ...,
avatarUrl: ...,
hasPremium: true,
}
}
With data duplicated, you often need a mechanism to keep duplicate data up-to-date from its "source". So you might consider a Cloud Function trigger for onUpdate() of User documents, and when a relevant value is modified (email, displayName, avatarUrl, and/or hasPremium) then you would loop through all questions that are postedBy that user and update accordingly.
The rules-of-thumb here are:
all data needed for one screen/function in your app goes into a SINGLE document
NoSQL document stores are used where reads are frequent and writes are infrequent
NoSQL data stores (typically) do not have "joins" - so don't design your app to require them (which is what your code above is doing: joining Question and Users)
often you don't care about updating ALL instances of duplicated data (e.g. if a user updates their displayName today, should you update a Question they posted 3 years ago? -- different apps/business needs will give different answers)

React Apollo: Update lists cache (with variables) when new item is added

I'm having a hard time figuring out how to update a list of items (in the cache). When a new item is created with react-apollo.
The <CreateItemButton /> component is (in my case) not nested within <ListItems /> component. From what I can figure out, I need to update the cache via the update function in my createItemButton <Mutation /> component.
The problem is when I try to store.readQuery({query: GET_LIST_ITEMS, variables: ???}) to get the current list of items (to append my recently created item), this can have variables/filters (for pagination, sorting etc.).
How do I know what variable to pass to the store.readQuery function, is there a sort of "last used variables" around this, or do you have any other suggestion on how to solve this issue?
This is a known Apollo issue and the team is working on it. The current options you have can be found in this medium post.
It is also an issue with deleting/updating an item when you have it in a list with filters or pagination.
Here is a reference of an opened issue on Github about it
This seems to work, but a bit hacky to access the cache.watches Set? :S
The trick is to access the cache's variables for a given query, save it and perform a refetch with the given query variables. In my case after creation of an item:
export const getGraphqlCacheVariables = (queryName, cache) => {
if (cache.watches) {
const watch = [...cache.watches].find(w => !!w.query[queryName]);
if (watch) {
return watch.variables;
}
}
};
The mutation update function:
let variables;
const update = (cache, { data }) => {
if (data && data.createTask && data.createTask.task) {
variables = getGraphqlCacheVariables('GetTasks', cache);
}
}
And the refetchQueries function:
const refetchQueries = () => {
if (variables && Object.keys(variables).length > 0) {
return [{ query: GET_TASKS, variables }];
}
return [];
}
Bot the update and refetchQueries functions should have access to the variables variable
The benefit compared to the solution in the Medium article, is that you're not deleting the cached data for a given query (GET_TASKS) with different variables.
Apollo now provides a new directive that allows ignoring of some variables when querying the cache. Check it here. I've used it to ignore my changing pagination params.

Problems with parsing a JSON Array after retrieving from firebase when using ionic-selectable

I'm currently developing an App using Ionic 3 and Firebase. I'm using ionic-selectable (you can see my stackblitz here) for the user to select an option from my firebase database array and return the selected option to the user's id.
I have got everything to work, except that ionic-selectable is not reading the retrieved array form firebase.
I'm retrieving the array using the following code:
this.itemsRefdiag = afg.list('medicalhx');
this.items = this.itemsRefdiag.snapshotChanges().map(changes => {
return changes.map(c => ({ ...c.payload.val() }));
});
const dgRef = this.afg.database.ref();
dgRef.child('medicalhx').orderByChild('name').on('value', snapshot => { this.snapshot2 = JSON.stringify(snapshot).replace(/"[0-9]":{"name":|{|}/g, ""); })
My console.log results in:
"Hyperthyroidism","Hypothyroidism","Diabetes Type 1","Diabetes Type 2"
However, when using ionic-selectable for private diagnoses: Diagnosis[] = [this.snapshot2], I get 'undefined' options. However, when I manually type in private diagnoses: Diagnosis[] = ["Hyperthyroidism","Hypothyroidism","Diabetes Type 1","Diabetes Type 2"], it works. I also tried parsing the JSON array using the following code instead:
this.itemsRefdiag = afg.list('medicalhx');
this.items = this.itemsRefdiag.snapshotChanges().map(changes => {
return changes.map(c => ({ ...c.payload.val() }));
});
const dbRef = this.afg.database.ref();
dbRef.child('medicalhx').orderByChild('name').on('value', snapshot =>
{ let snapshot3 = JSON.stringify(snapshot).replace(/"}/g, `"`);
let snapshot4 = snapshot3.replace(/"[0-9]":{"name":|{|}|"/g, "");
this.snapshot2 = snapshot4.split(",");
});
My console.log results in an Object with separate strings (an array):
["Hyperthyroidism","Hypothyroidism","Diabetes Type 1","Diabetes Type 2"]
However, ionic-selectable still doesn't seem to read that and I get undefined error. Any ideas on what I may be doing wrong with the array?
EDIT
It actually does work the second time around, however the console error pops up the first time and I believe this is because it's not waiting for the array results to pop-up the first time around. Is there a way to add a wait time until the array loads?
The second code listed in the question converting it to a real array worked but required a loading time, hence poping up a console error. I managed to get around the loading time issue by implementing Asynchronous searching as per this stackblitz.

Firebase cloud function not updating record

imagine this scenario:
You have a list with lets say 100 items, a user favorites 12 items from the list.
The user now wants these 12 items displayed in a new list with the up to date data (if data changes from the original list/node)
What would be the best way to accomplish this without having to denormalize all of the data for each item?
is there a way to orderByChild().equalTo(multiple items)
(the multiple items being the favorited items)
Currently, I am successfully displaying the favorites in a new list but I am pushing all the data to the users node with the favorites, problem is when i change data, their data from favorites wont change.
note - these changes are made manually in the db and cannot be changed by the user (meta data that can potentially change)
UPDATE
I'm trying to achieve this now with cloud functions. I am trying to update the item but I can't seem to get it working.
Here is my code:
exports.itemUpdate = functions.database
.ref('/items/{itemId}')
.onUpdate((change, context) => {
const before = change.before.val(); // DataSnapshot after the change
const after = change.after.val(); // DataSnapshot after the change
console.log(after);
if (before.effects === after.effects) {
console.log('effects didnt change')
return null;
}
const ref = admin.database().ref('users')
.orderByChild('likedItems')
.equalTo(before.title);
return ref.update(after);
});
I'm not to sure what I am doing wrong here.
Cheers!
There isn't a way to do multiple items in the orderByChild, denormalising in NoSQL is fine its just about keeping it in sync like you mention. I would recommend using a Cloud Function which will help you to keep things in sync.
Another option is to use Firestore instead of the Realtime Database as it has better querying capabilities where you could store a users id in the document and using an array contains filter you could get all the users posts.
The below is the start of a Cloud function for a realtime database trigger for an update of an item.
exports.itemUpdate = functions.database.ref('/items/{itemId}')
.onUpdate((snap, context) => {
// Query your users node for matching items and update them
});

Resources