Fetching Reference Value from Firebase in React Native - reactjs

Hi I am currently struggling to retrieve the data of a specific field from a collection.
This is the structure of my data:
And my RN code:
onEventCollectionUpdate = (querySnapshot) => {
const events = [];
querySnapshot.forEach((doc) => {
const { user, title, action, verb, latitude, longitude, photo } = doc.data();
// const data = doc.data();
events.push({
id: doc.id,
title,
user,
verb,
latitude,
longitude,
photo
});
});
this.setState({
events,
isLoading: false,
});
}
which is triggered in:
componentDidMount() {
this.unsubscribe = this.events.onSnapshot(this.onEventCollectionUpdate);
}
which is defined in my constructor:
this.events = firebase.firestore().collection('events')
I can retrieve the data, and when I print user or doc.data(), the results are massive and nearly incomprehensible. The const { user, title... bit works fine, except "user."
User is returned as a referenceValue, but doesn't actually return as if it were a value I can do anything with. It seems to be returning a massive object, which I can't figure out how to deal with.
Since it is a reference, is it returning the full users object? If so, how do I deal with that? If it's not, how do I make use of the user that is returned? Ideally I end up with the data from user, but not sure if I need to make extra calls for that.
Any pointers would be great, thank you!

Related

Firebase does not save empty array

Hello I have a question I am making app with Firebase and React. The problem is that I want to have comment array but it is empty when the item is created. How can I solve this problem and have an empty array and then update this array after adding comments.
There is a lot of code.
const onSubmitHandler = (e: React.FormEvent): void => {
e.preventDefault();
if (user?.displayName) {
const recipe: Recipe = {
username: user.displayName,
title: title,
type: type,
description: description,
id: Math.random(),
time: time,
ingredients: ingredients,
stars: Math.floor(Math.random() * 6) + 1,
steps: steps,
comments: [],
};
dispatch(recipeAction.addRecipe(recipe));
dispatch(sendData(recipe));
navigate("/");
}
};
Redux action
export const fetchRecipes = () => {
return async (dispatch: ThunkDispatch<{}, {}, AnyAction>) => {
dispatch(uiAction.isLoading(true));
const getRecipes = async () => {
const response = await fetch(
*FIREBASE API*
);
const data = response.json();
return data;
};
try {
const data = await getRecipes();
console.log(data);
if (data) {
for (const key of Object.keys(data)) {
dispatch(recipeAction.replaceRecipes(data[key]));
}... other not needed code.
Redux slice
replaceRecipes(state, action: PayloadAction<Recipe>) {
const fetchedRecipe = action.payload;
state.recipes.push(fetchedRecipe);
},
Why won't it save an empty array?
I don't have so much knowledge so I am just explaining it in Java.
When you create a new empty array like this:
List array = new ArrayList<>();
its size is 0. And, 0 can in other words be said null for an empty array. Now, Firebase ignores null or 0 sized array as it had nothing to display in the console.
Then what should I do?
It is not a hard thing to do that. Now, in your case, because there is no such key in the database, it will give null and then you try using it you'll get an exception. This is what every developer wants to prevent. So the solution can be to store a boolean in the database with the other keys to check if a data has the array or not. When the post is just created, there are no comments. You can set it to false. Now, when a user adds a comment, set it to true.
Now when fetching the comments, first check if the boolean is true or not. If true, fetch the comments. Or else just ignore.
Code?
As mentioned, I have not used React.JS so much so can't provide the code. But I hope that you will be able to do it as you have already done so much of it by yourself.

Writing to Firebase Firestore React

so I have a problem right now. I'm entering the users dates into cloud firestore like this:
so this is a user collection, with a document by user's id's and then the dates are entered as a list. But whenever I refresh the page and enter new data, all the previous data disappears.
So I'm wondering how do I enter data so that it goes like collection(userCalendar).doc(USERID).collection(dates) and then it has all the user's data entered as strings rather than an array like I've been doing.
My code for the way it's behaving right now is below. Thank you! :)
export const allEvents = [];
const Calendar = () => {
const [date, setData] = useState([]);
const handleDateClick = async (DateClickArg) => {
if (DateClickArg.jsEvent.altKey) {
const title = prompt("Enter title", DateClickArg.dateStr); // allows user to put a title in
// making object
const event = {
title: title ? title : DateClickArg.dateStr,
start: DateClickArg.date,
allDay: true
}
allEvents.push(event)
const db = fire.firestore();
let currentUserUID = fire.auth().currentUser.uid
const doc = await fire
.firestore()
.collection("userCalendar")
.doc(currentUserUID)
.get()
db.collection("userCalendar")
.doc(currentUserUID)
.set({
activites: allEvents
})
}
}
You can use arrayUnion() to add new items to an array however it'll be difficult for you to query activities of a user.
For example, you cannot fetch a single activity from that array but you'll have to fetch all of them get the required one. Additionally, you cannot update an object in an array directly in Firestore.
Also a document has a max size limit of 1 MB so if a user can have many activities, it'll be best to create sub-collection.
I would recommend restructuring the following way:
users -> { userId } -> activities-> { activityId }
(col) (doc) (col) (doc)
All of user's activities/events are now in a sub-collection "activities" and each activity would be a document instead of an array item. With this you can easily read/update/delete a single activity.
Also checkout: Firestore subcollection vs array
Not sure whether this meets your requirement, but from my understanding you just want to update the activities with the allEvents which contains all the updated activities.
db.collection("userCalendar")
.doc(currentUserUID)
.set({
activites: allEvents
})
should become
db.collection("userCalendar")
.doc(currentUserUID)
.set({
activites: allEvents
}, { merge: true })
Or you can use the update method
db.collection("userCalendar")
.doc(currentUserUID)
.update({
activites: allEvents
})
From the docs
To update some fields of a document without overwriting the entire document, use the update() method:
import { doc, setDoc } from "firebase/firestore";
const cityRef = doc(db, 'cities', 'BJ');
setDoc(cityRef, { capital: true }, { merge: true });
It looks like you're overwriting your collection with every code execution:
db.collection("userCalendar")
.doc(currentUserUID)
.set({
activites: allEvents
})
You should consider to make an array union, so that the values are added to your collection instead of overwriting them:
db.collection("userCalendar")
.doc(currentUserUID)
.update({
activites: firebase.firestore.FieldValue.arrayUnion(
{
allEvents
}),
})
Also some examples from firestore docu:
https://cloud.google.com/firestore/docs/samples/firestore-data-set-array-operations

How do I structure a fetch request after an other?

I have a React blog and I am using Firebase as a back-end. I use createUserWithEmailAndPassword method and after authentication users are redirected to blog where they can start adding blogs.
I store their posts in a firestore collection "posts". No problem with that.
I also want a user object to be created after login with the user preferences. Let's say a specific theme each one has chosen.
I create a collection called "users" at firestore and where I will store each ones object {user: "random#hotmail.com, theme: "dark" ,isAdmin: false, etc} using addDoc method.
I want this object to be created once though and not every time a user logs in.
How do the check on that "users" collection if the user already exists?
I get the collection getDocs(userCollectionRef) and then I filter the data let's say by a property to see if there is that object there.
And if not I want to add the document using addDoc method.
this is the request:
useEffect(() => {
const createUserData = async () => {
const data = await getDocs(usersCollectionRef);
const docs = data.docs.map((doc) => ({
...doc.data(),
id: doc.id,
}));
const userData = docs.filter((doc) => doc.user === user.email);
if (userData.length === 0) {
await addDoc(usersCollectionRef, {
user: user.email,
isAdmin: false,
theme: "dark",
});
}
};
if (user) {
createUserData();
}
}, [user, usersCollectionRef]);
It seems like I am trying
to check and add to the collection at the same time and this is why it doesn't work.
Any ideas?
Should I have an in-between step where I store what I'm getting from the getDocs in a state or something and then do the second request?
Can anyone explain please?
I changed the if statement to a return like so and it worked.
return (
userData.length === 0 &&
(await addDoc(usersCollectionRef, {
user: user.email,
wordGoal: wordGoal,
}))
);
I guess I return the second promise after the first now that's why
I guess when the getDocs request happens because its asynchronous the data then are empty then the code proceeds to the if statement which at that point is true (we have no records) and so executes it.

Lookup multiple Firebase entries using Redux action

I have this Redux action, which looks up a user and returns all the items they have:
export const itemsFetch = () => {
const { currentUser } = firebase.auth();
return dispatch => {
firebase
.database()
.ref(`users/${currentUser.uid}/items`)
.on('value', snapshot => {
dispatch({ type: ITEMS_FETCH_SUCCESS, payload: snapshot.val() });
});
};
};
Works great, and each item returned has a unique key associated with it.
I want to modify this action to look up specific items, which I've done. That works fine too:
export const itemLookup = uid => {
const { currentUser } = firebase.auth();
return dispatch => {
firebase
.database()
.ref(`users/${currentUser.uid}/items/${uid}`)
.on('value', snapshot => {
dispatch({ type: ITEM_LOOKUP_SUCCESS, payload: snapshot.val() });
});
};
};
This also works fine, but I can only use this to lookup a single item.
I want to loop over an array of item ids, and lookup details for each. Doing this from a component, and using mapStateToProps, causes the component to rerender each time, losing the previous lookup in the process.
Is it best to loop over the ids I have at a component level, and make multiple calls. Or should I pass the array to the action, and somehow loop over them within the action?
Thanks
I feel like I'm doing something dumb, or misunderstanding Redux completely.
In my opinion, this is one of the few limitations that firebase has (along side with queries) that sometimes make me want to grow hair again and lose it (I am bald).
I am more experienced with Firestore although I have used Database, but I think you are correct that you can only request one item in Firebase. What I would do to solve this, is to create a new action that receives an array of IDs and then executes and array of promises that will query each doc.
Something like (pseudo code, and you might need to wrap your firebase call into a promise):
let promises = [];
arrayIds.forEach(id => {
promises.push(firebase.database().ref.get(id))
})
return Promise.all(promises).then(dispatch(results))
Now, if you find that the amount of results are usually not a lot, it is totally fine (and usually the way Firebase requires you to) to complete the data filtering in the client.
Using the response from sfratini, I managed to work it out. Here's the action:
export const itemLookup = oid => {
const { currentUser } = firebase.auth();
const items = []
return dispatch => {
new Promise(function (res, rej) {
oid.forEach(id => {
firebase.database().ref(`users/${currentUser.uid}/items/${id}`).on('value', snapshot => {
items.push(snapshot.val())
})
})
}).then(dispatch({ type: ITEM_LOOKUP_SUCCESS, payload: items }))
}
I used the same reducer, and now the items array makes it down to component level. Perfect!

What is a better way to add property to objects in array in response from API, React.js?

I'm trying to make an app using API from https://swapi.co/
They have no pictures in their response so I thought to add them myself after fetching the data.
I'm fetching data from API in action like that
export const fetchCharacters = () => async dispatch => {
const response = await fetch('https://swapi.co/api/people/');
const data = await response.json();
const results = await data.results;
dispatch({
type: FETCH_CHARACTERS,
payload: results
});
}
After that in reduce
case FETCH_CHARACTERS :
return {
...state,
characters: action.payload
}
To load this data to my component:
componentDidMount() {
this.props.fetchCharacters();
}
After this this.props.characters is an array of objects.
So I've been thinking how can I add pictures for each character?
I'm thinking something like mapping over objects, adding new property to each one. And to assign right picture to character I'm thinking naming pictures 0.jpg, 1.jpg, and then in map function just doing somehing like:
character.pic = `link/${index}.jpg`;
Is it a good way of achieving this? Or is there a better way?
you can not change props value. i am sharing 2 way that you can use.
One is
const resultObj = {};
resultObj.result = await data.results;
resultObj.imgPath = "path of image"
dispatch({
type: FETCH_CHARACTERS,
payload: resultObj
});
Second is here
this.setState({characters:this.props.fetchCharacters})
Now you can add key in state

Resources