how to get id of object from firebase database in reactjs
I have an array of list got from firebase database in react-redux , I want to get id of every object of array, How can I get?
Get the snapshot, and iterate through it as a Map, with Object.keys(foo).forEach for example.
Here is a dummy piece of code :
`
const rootRef = firebase.database().ref();
const fooRef = rootRef.child("foo");
fooRef.on("value", snap => {
const foo = snap.val();
if (foo !== null) {
Object.keys(foo).forEach(key => {
// The ID is the key
console.log(key);
// The Object is foo[key]
console.log(foo[key]);
});
}
});
`
Be careful with Arrays in Firebase : they are Maps translated into Arrays if the IDs are consecutive numbers started from '0'. If you remove an item in the middle of your array, it will not change the ID accordingly. Better work with Maps, it's more predictable.
You could try something like this:
export const getAllRooms = () => {
return roomCollection.get().then(function (querySnapshot) {
const rooms = [];
querySnapshot.forEach(function (doc) {
const room = doc.data();
room.id = doc.id;
rooms.push(room);
});
return rooms;
});
};
`
Related
The function collects role Assignment PrincipalIds on an item in SPO. I then use a foreach to populate state with the Title's of these PrincipalIds. This all works fine but it's inefficient and I'm sure there is a better way to do it than rendering multiple times.
private _permJeChange = async () => {
if(this.state.userNames){
this.setState({
userNames: []
});
}
var theId = this.state.SelPermJEDD;
var theId2 = theId.replace('JE','');
var info = await sp.web.lists.getByTitle('MyList').items.getById(theId2).roleAssignments();
console.log(info, 'info');
var newArr = info.map(a => a.PrincipalId);
console.log(newArr, 'newArr');
// const userIds = [];
// const userNames = [];
// const userNameState = this.state.userNames;
newArr.forEach(async el => {
try {
await sp.web.siteUsers.getById(el).get().then(u => {
this.setState(prevState => ({
userNames: [...prevState.userNames, u.Title]
}));
// userNames.push(u.Title);
// userIds.push(el);
});
} catch (err) {
console.error("This JEForm contains a group");
}
});
}
I've left old code in there to give you an idea of what I've tried. I initially tried using a local variable array const userNames = [] but declaring it locally or even globally would clear the array everytime the array was populated! So that was no good.
PS. The reason there is a try catch is to handle any SPO item that has a permissions group assigned to it. The RoleAssignments() request can't handle groups, only users.
Create an array of Promises and await them all to resolve and then do a single state update.
const requests = info.map(({ PrincipalId }) =>
sp.web.siteUsers.getById(PrincipalId).get().then(u => u.Title)
);
try {
const titles = await Promise.all(requests);
this.setState(prevState => ({
userNames: prevState.userNames.concat(titles),
}));
} catch (err) {
console.error("This JEForm contains a group");
}
I am trying to use react-admin. I am not an expert on React.
I want to keep a list of items updated with the firestore (firebase) database in realtime (using the onSnapshot listener by firestore)
Up to now, I am able to use react-admin to show a list of items from firestore (firebase) database and showing them on a list, but I don't know where to put the listener and how to keep the list updated in realtime.
What I have up to now is a dataProvider for react-admin like this:
const dataProvider = {
getList: async (resourceName, params) => {
console.log(params);
if (params.pagination) {
let values = [];
//Firestore Query
let snapshots = await firebase
.firestore()
.collection("items")
.get();
for (const snapshot of snapshots.docs) {
const data = snapshot.data();
values.push(data);
}
//End Firestore query
if (params.filter) {
values = values.filter((item) => {
let meetsFilters = true;
for (const key of Object.keys(params.filter)) {
meetsFilters = item[key] === params.filter[key];
}
return meetsFilters;
});
}
if (params.sort) {
values.sort(
sortBy(
`${params.sort.order === "ASC" ? "" : "-"}${params.sort.field}`
)
);
}
const keys = values.map((i) => i.id);
const { page, perPage } = params.pagination;
const _start = (page - 1) * perPage;
const _end = page * perPage;
const data = values ? values.slice(_start, _end) : [];
const ids = keys.slice(_start, _end) || [];
const total = values ? values.length : 0;
console.log({ data, ids, total });
return { data, ids, total };
}
}
I want to insert a listener "onSnapshot" by Firebase replacing the Firestore Query in my code.
The listener onSnapshot is fired every time a new document is added in the firestore collection. It should be like this (according to https://firebase.google.com/docs/firestore/query-data/listen)
firebase.firestore().collection("items")
.onSnapshot(function(querySnapshot) {
var cities = [];
querySnapshot.forEach(function(doc) {
//Do Something
});
});
How could I insert this listener in place of the Firestore Query in my code?
The problem is that if I put it directly in place of the Firestore Query, the getList method never reaches the return statement.
I want to have same functionality using map function. Following is my code:
async componentDidMount() {
const data = await TodoAPI.getTodo();
for(var i=0;i<data.response.length;i++)
{
if(data.response[i]._id === this.props.match.params.id)
{
this.setState({
todo_description: data.response[i].todo_description,
todo_responsible: data.response[i].todo_responsible,
todo_priority: data.response[i].todo_priority,
todo_completed: data.response[i].todo_completed
})
}
}
}
I guess you’re a bit confused, as map() would not be useful in this case. It creates a new array but what you want (correct me if I’m wrong) is to find the item, so find() seems to be what you need:
async componentDidMount() {
const data = await TodoAPI.getTodo();
const id = this.props.match.params.id;
const item = data.response.find(item => item._id === id);
this.setState(item);
}
I am using firebase cloud firestore for storing data. And I am developing a web app using reactjs. I have obtained documents using the following function:
getPeoples() {
let persons = [];
firestore.collection("students")
.get()
.then(function (querySnapshot) {
querySnapshot.forEach((doc) => {
var person = {};
person.name = doc.data().Name;
person.course = doc.data().Course;
persons.push(person);
})
});
console.log(persons);
return persons;
}
I am getting the desired data, but when I am iterating through persons array, it says it has length of 0.
here is the console output when I am displaying complete persons array and its length.
The length should be 14, but it shows 0. Please correct me what is wrong with me?
I want to display the output in the html inside the render() method of react component.
The output of
const peoples = this.getPeoples();
console.log(peoples);
It is:
The complete render method looks like:
render() {
const peoples = this.getPeoples();
console.log(peoples);
return (
<div className="peopleContainer">
<h2>Post-Graduate Students</h2>
{/* <h4>{displayPerson}</h4> */}
</div>
);
}
This is due to the fact the query to the database is asynchronous and you are returning the persons array before this asynchronous task is finished (i.e. before the promise returned by the get() method resolves).
You should return the persons array within the then(), as follows:
getPeoples() {
let persons = [];
return firestore.collection("students")
.get()
.then(function (querySnapshot) {
querySnapshot.forEach((doc) => {
var person = {};
person.name = doc.data().Name;
person.course = doc.data().Course;
persons.push(person);
})
console.log(persons);
return persons;
});
}
And you need to call it as follows, because it will return a promise :
getPeoples().then(result => {
console.log(result);
});
Have a look at what is written to the console if you do:
console.log(getPeoples());
getPeoples().then(result => {
console.log(result);
});
I'm not sure but please try to update your
getPeoples() {
let persons = [];
firestore.collection("students")
.get()
.then(function (querySnapshot) {
querySnapshot.forEach((doc) => {
var person = {};
person.name = doc.data().Name;
person.course = doc.data().Course;
persons.push(person);
})
});
console.log(persons);
return persons;
}
to
getPeoples() {
let persons = [];
firestore.collection("students")
.get()
.then(querySnapshot => {
querySnapshot.forEach((doc) => {
persons.push({name = doc.data().Name,
course = doc.data().Course
})
});
console.log(persons);
return persons;
}
Update
Sorry I thought you have problem with filling persons array in your function. Anyway as Renaud mentioned the query in your function is asynchronous so the result is not quick enough to be displayed on render. I use similar function and I found redux the best way to handle this situations.
Let's say we have a root collection named 'todos'.
Every document in this collection has:
title: String
subcollection named todo_items
Every document in the subcollection todo_items has
title: String
completed: Boolean
I know that querying in Cloud Firestore is shallow by default, which is great, but is there a way to query the todos and get results that include the subcollection todo_items automatically?
In other words, how do I make the following query include the todo_items subcollection?
db.collection('todos').onSnapshot((snapshot) => {
snapshot.docChanges.forEach((change) => {
// ...
});
});
This type of query isn't supported, although it is something we may consider in the future.
If anyone is still interested in knowing how to do deep query in firestore, here's a version of cloud function getAllTodos that I've come up with, that returns all the 'todos' which has 'todo_items' subcollection.
exports.getAllTodos = function (req, res) {
getTodos().
then((todos) => {
console.log("All Todos " + todos) // All Todos with its todo_items sub collection.
return res.json(todos);
})
.catch((err) => {
console.log('Error getting documents', err);
return res.status(500).json({ message: "Error getting the all Todos" + err });
});
}
function getTodos(){
var todosRef = db.collection('todos');
return todosRef.get()
.then((snapshot) => {
let todos = [];
return Promise.all(
snapshot.docs.map(doc => {
let todo = {};
todo.id = doc.id;
todo.todo = doc.data(); // will have 'todo.title'
var todoItemsPromise = getTodoItemsById(todo.id);
return todoItemsPromise.then((todoItems) => {
todo.todo_items = todoItems;
todos.push(todo);
return todos;
})
})
)
.then(todos => {
return todos.length > 0 ? todos[todos.length - 1] : [];
})
})
}
function getTodoItemsById(id){
var todoItemsRef = db.collection('todos').doc(id).collection('todo_items');
let todo_items = [];
return todoItemsRef.get()
.then(snapshot => {
snapshot.forEach(item => {
let todo_item = {};
todo_item.id = item.id;
todo_item.todo_item = item.data(); // will have 'todo_item.title' and 'todo_item.completed'
todo_items.push(todo_item);
})
return todo_items;
})
}
I have faced the same issue but with IOS, any way if i get your question and if you use auto-ID for to-do collection document its will be easy if your store the document ID as afield with the title field
in my case :
let ref = self.db.collection("collectionName").document()
let data = ["docID": ref.documentID,"title" :"some title"]
So when you retrieve lets say an array of to-do's and when click on any item you can navigate so easy by the path
ref = db.collection("docID/\(todo_items)")
I wish i could give you the exact code but i'm not familiar with Javascript
I used AngularFirestore (afs) and Typescript:
import { map, flatMap } from 'rxjs/operators';
import { combineLatest } from 'rxjs';
interface DocWithId {
id: string;
}
convertSnapshots<T>(snaps) {
return <T[]>snaps.map(snap => {
return {
id: snap.payload.doc.id,
...snap.payload.doc.data()
};
});
}
getDocumentsWithSubcollection<T extends DocWithId>(
collection: string,
subCollection: string
) {
return this.afs
.collection(collection)
.snapshotChanges()
.pipe(
map(this.convertSnapshots),
map((documents: T[]) =>
documents.map(document => {
return this.afs
.collection(`${collection}/${document.id}/${subCollection}`)
.snapshotChanges()
.pipe(
map(this.convertSnapshots),
map(subdocuments =>
Object.assign(document, { [subCollection]: subdocuments })
)
);
})
),
flatMap(combined => combineLatest(combined))
);
}
As pointed out in other answers, you cannot request deep queries.
My recommendation: Duplicate your data as minimally as possible.
I'm running into this same problem with "pet ownership". In my search results, I need to display each pet a user owns, but I also need to be able to search for pets on their own. I ended up duplicated the data. I'm going to have a pets array property on each user AS WELL AS a pets subcollection. I think that's the best we can do with these kinds of scenarios.
According to docs, you needs to make 2 calls to the firestore.. one to fetch doc and second to fetch subcollection.
The best you can do to reduce overall time is to make these two calls parallelly using promise.All or promise.allSettled instead of sequentially.
You could try something like this:
db.collection('coll').doc('doc').collection('subcoll').doc('subdoc')