Javascript - console data shows length of 2 but contains only one value - javascript

I have the following firestore code, which simply appends some object into an array, which is then passed to a react component for populating a list.
export const getUsers =async (tarinerId) => {
let data = [];
try{
let ref = await db
.collection("users")
.orderBy("createdAt")
.limit(3)
.get();
if (ref.empty) {
return new Promise.reject("No User Found")
}
ref.forEach(doc => {
data.push({
name: doc.data().first_name+" "+doc.data().last_name,
id: doc.data().id,
email: doc.data().email,
createdAt: doc.data().createdAt
})
}
console.log(data)
return Promise.resolve({userData: data})
} catch(error) {console.log(error}
}
And the userData is passed into a react component. Once upon receiving i want to pop out one last element from the array.
<Component userData={userData.data} />
And inside the Component I am poping out the data.
But the issue is console.log from the function is printing out a length of 4 items, but there are only 3 items inside it.
My first guess is about call by reference, but how come the component pop affects the console.log on the function which gets executed already? Or is there something obvious that I am missing here?

Your ref might have an empty record. That might be what returning an extra empty object

Related

Why changes made in database not updates in realtime?

I am trying to learn firestore realtime functionality.
Here is my code where I fetch the data:
useEffect(() => {
let temp = [];
db.collection("users")
.doc(userId)
.onSnapshot((docs) => {
for (let t in docs.data().contacts) {
temp.push(docs.data().contacts[t]);
}
setContactArr(temp);
});
}, []);
Here is my database structure:
When I change the data in the database I am unable to see the change in realtime. I have to refresh the window to see the change.
Please guide me on what I am doing wrong.
Few issues with your useEffect hook:
You declared the temp array in the way that the array reference is persistent, setting data with setter function from useState requires the reference to be new in order to detect changes. So your temp array is updated (in a wrong way btw, you need to cleanup it due to now it will have duplicates) but React is not detectign changes due to the reference to array is not changed.
You are missing userId in the dependency array of useEffect. If userId is changed - you will continue getting the values for old userId.
onSnapshot returns the unsubscribe method, you have to call it on component unMount (or on deps array change) in order to stop this onSnapshot, or it will continue to work and it will be a leak.
useEffect(() => {
// no need to continue if userId is undefined or null
// (or '0' but i guess it is a string in your case)
if (!userId) return;
const unsub = db
.collection("users")
.doc(userId)
.onSnapshot((docs) => {
const newItems = Object.entries(
docs.data().contacts
).map(([key, values]) => ({ id: key, ...values }));
setContactArr(newItems);
});
// cleanup function
return () => {
unsub(); // unsubscribe
setContactArr([]); // clear contacts data (in case userId changed)
};
}, [userId]); // added userId

How to get out a certain field value from a object from a array in Javascript

I'm learning firebase cloud functions with JavaScript,
I get a QuerySnapshot back of a collection of documents each document holds an ID field and a message field.
Where I'm stuck now is that every time I loop through the collection I want to be able to just take the ID field out from each object and save it.
I've tried all the ways that I can think of that come up on Google and stack overflow none are working for me, I'm obviously doing something wrong.
I'm totally new to JavaScript so this may be an easy fix if anyone has any information
This is my code in visual studio that I'm using, which is working fine from where I can see to get to the collection that I need to
// onDelete is my trigger which would then go and fetch the collection that I want
exports.onDelet = functions.firestore.document('recentMsg/currentuid/touid/{documentId}').onDelete(async(snap, context) => {
const data = snap.data();
const contex = context.params;
// once I get the information from onDelete the following code starts
await admin.firestore().collection(`messages/currentuid/${contex.documentId}`)
.get()
.then((snapshot) => {
//this array will hold all documents from the collection
const results = []
const data = snapshot.docs.map((doc) => ({
id: doc.id,
...doc.data(),
}));
results.push(data)
console.log(results)
//This is one of the ways I've tried
//results.forEach((doc) => {
//console.log(doc.id)
//this is a print out in the terminal
// > undefined
// } );
});
Below is a print out that I get in terminal which is all the information that it holds which is great,
But really all I want is to have an array that holds every id
if there was just one value in the array I know this would not be a problem but because there is an object with multiple values that's the issue.
i functions: Beginning execution of "onDelet"
> [
> [
> { id: '28ZyROMQkzEBBDwTm6yV', msg: 'sam 2' },
> { id: 'ixqgYqmwlZJfb5D9h8WV', msg: 'sam 3' },
> { id: 'lLyNDLLIHKc8hCnV0Cgc', msg: 'sam 1' }
> ]
> ]
i functions: Finished "onDelet" in ~1s
once again apologies if this is a dumb question I'm a newbie.
With await admin.firestore().collection(messages/currentuid/${contex.documentId}) .get() you get a QuerySnapshot that supports forEach and not map. Just write your code like this:
// onDelete is my trigger which would then go and fetch the collection that I want
exports.onDelet = functions.firestore.document('recentMsg/currentuid/touid/{documentId}').onDelete(async(snap, context) => {
const data = snap.data();
const contex = context.params;
// once I get the information from onDelete the following code starts
await admin.firestore().collection(`messages/currentuid/${contex.documentId}`)
.get()
.then((snapshot) => {
//this array will hold all documents from the collection
const results = []
snapshot.forEach((doc) =>{
results.push({id:doc.id,...doc.data()})
});
console.log(results)
//This is one of the ways I've tried
//results.forEach((doc) => {
//console.log(doc.id)
//this is a print out in the terminal
// > undefined
// } );
});
I assume that messages/currentuid/${contex.documentId} is a collecion and not a single document.
You can read more baout it here.

React.JS, how to edit the response of a first API call with data from a second API call?

I need to display some data in my component, unfortunately the first call to my API returns just part of the information I want to display, plus some IDs. I need another call on those IDs to retrieve other meaningful data. The first call is wrapped in a useEffect() React.js function:
useEffect(() => {
const getData = async () => {
try {
const { data } = await fetchContext.authAxios.get(
'/myapi/' + auth.authState.id
);
setData(data);
} catch (err) {
console.log(err);
}
};
getData();
}, [fetchContext]);
And returns an array of objects, each object representing an appointment for a given Employee, as follows:
[
{
"appointmentID": 1,
"employeeID": 1,
"customerID": 1,
"appointmentTime": "11:30",
"confirmed": true
},
... many more appointments
]
Now I would like to retrieve information about the customer as well, like name, telephone number etc. I tried setting up another method like getData() that would return the piece of information I needed as I looped through the various appointment to display them as rows of a table, but I learned the hard way that functions called in the render methods should not have any side-effects. What is the best approach to make another API call, replacing each "customerID" with an object that stores the ID of the customer + other data?
[Below the approach I've tried, returns an [Object Promise]]
const AppointmentElements = () => {
//Loop through each Appointment to create a single row
var output = Object.values(data).map((i) =>
<Appointment
key={i['appointmentID'].toString()}
employee={i["employeeID"]} //returned a [Object premise]
customer={getEmployeeData((i['doctorID']))} //return a [Object Promise]
time={index['appointmentTime']}
confirmed = {i['confirmed']}
/>
);
return output;
};
As you yourself mentioned functions called in the render methods should not have any side-effects, you shouldn't be calling the getEmployeeData function inside render.
What you can do is, inside the same useEffect and same getData where you are calling the first api, call the second api as well, nested within the first api call and put the complete data in a state variable. Then inside the render method, loop through this complete data instead of the data just from the first api.
Let me know if you need help in calling the second api in getData, I would help you with the code.
Update (added the code)
Your useEffect should look something like:
useEffect(() => {
const getData = async () => {
try {
const { data } = await fetchContext.authAxios.get('/myapi/' + auth.authState.id);
const updatedData = data.map(value => {
const { data } = await fetchContext.authAxios.get('/mySecondApi/?customerId=' + value.customerID);
// please make necessary changes to the api call
return {
...value, // de-structuring
customerID: data
// as you asked customer data should replace the customerID field
}
}
);
setData(updatedData); // this data would contain the other details of customer in it's customerID field, along with all other fields returned by your first api call
} catch (err) {
console.log(err);
}
};
getData();
}, [fetchContext]);
This is assuming that you have an api which accepts only one customer ID at a time.
If you have a better api which accepts a list of customer IDs, then the above code can be modified to:
useEffect(() => {
const getData = async () => {
try {
const { data } = await fetchContext.authAxios.get('/myapi/' + auth.authState.id);
const customerIdList = data.map(value => value.customerID);
// this fetches list of all customer details in one go
const customersDetails = (await fetchContext.authAxios.post('/mySecondApi/', {customerIdList})).data;
// please make necessary changes to the api call
const updatedData = data.map(value => {
// filtering the particular customer's detail and updating the data from first api call
const customerDetails = customersDetails.filter(c => c.customerID === value.customerID)[0];
return {
...value, // de-structuring
customerID: customerDetails
// as you asked customer data should replace the customerID field
}
}
);
setData(updatedData); // this data would contain the other details of customer in it's customerID field, along with all other fields returned by your first api call
} catch (err) {
console.log(err);
}
};
getData();
}, [fetchContext]);
This will reduce the number of network calls and generally preferred way, if your api supports this.

Receiving undefined when retrieving value from firebase

Where is my call wrong? The first console.log leads to the role object and the second console.log leads to undefined. When it should be the user.
componentDidMount(){
let user = fire.auth().currentUser;
let db = fire.database();
let roleRef = db.ref('/roles');
roleRef.orderByChild('user').equalTo(user.uid).once('value', (snapshot) => {
console.log(snapshot.val())
console.log(snapshot.val().user);
})
}
Result:
Firebase:
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.
Your code doesn't take the list into account. The easiest way to do so is with Snapshot.forEach():
roleRef.orderByChild('user').equalTo(user.uid).once('value', (snapshot) => {
snapshof.forEach((roleSnapshot) => {
console.log(roleSnapshot.val())
console.log(roleSnapshot.val().user);
});
})

React - write and read nested objects in state

I am developing a React app that takes data from a database on Google's Firebase.
The database is organized this way:
users {
user 1 {
name
surname
...
}
user 2 {
name
surname
...
}
}
How I retrieve data in React:
I take location and instrument from the url to filter the results of the search (using just location for now, but would like to use both), and I successfully manage to get the object printed in the console.
componentWillMount(){
const searchResults = FBAppDB.ref('users');
let location = this.props.location;
let instrument = this.props.instrument;
let profiles = [];
searchResults.orderByChild('location').equalTo(location).on('value', (snap) => {
let users = snap.val();
Object.keys(users).map((key) => {
let user = users[key];
console.log(user);
profiles.push(user);
});
});
// this.setState({users: profiles[0]});
}
console output of the object:
Object {name: "Whatever", surname: "Surrname", ... }
What I seem to fail doing is to save these objects (could be more than one) in the state, so I can update the view when the user changes other filters on the page.
I tried to setState the actual user but the state results empty, any idea?
In addition to writing, what would be the best way to get the data back from the state? How do I loop through objects in the state?
Thanks
EDIT
This is how my render function looks like:
render(){
return(
<Row>
{
Object.keys(this.state.users).map(function (key) {
var user = this.state.users[key];
return <SearchResult user={user} />
})
}
</Row>
);
}
I tried to print this.state.users directly, but it doesn't work. Therefore I tried to come up with a (non-working) solution. I'm sorry for any noob mistake here.
I tried to setState the actual user but the state results empty, any
idea?
It is because your DB operation searchResult is asynchronous and JavaScript does not let the synchronous codes wait until the async code completes.
In your code, if you enable the commented setState invocation, that code will get executed while the async code is waiting for response.
You need to setState inside:
searchResults.orderByChild('location').equalTo(location).on('value', (snap) => {
let users = snap.val();
const profiles = Object.keys(users).map((key) => users[key]);
this.setState({ users: profiles[0] });
});
Whenever you want something to be executed after an async code, put that inside a callback (to the async fn) or use a Promise.
What would be the best way to get the data back from the state? How do I loop through objects in the state?
setState can be asynchronous too. But you will get the new updated state on next render. So inside render() you can access users in the state as this.state.users and perform your operations using it.
UPDATE:
componentWillMount(){
const searchResults = FBAppDB.ref('users');
const location = this.props.location;
const instrument = this.props.instrument;
searchResults.orderByChild('location').equalTo(location).on('value', (snap) => {
const users = snap.val();
const profiles = Object.keys(users).map((key) => users[key]);
// previously we were setting just profiles[0]; but I think you need
// the entire list of users as an array to render as search results
this.setState({ users: profiles }); // set the whole data to state
});
}
render(){
const users = this.state.users || []; // note that this.state.users is an array
return(
<Row>
{
users.length > 0
? users.map((user) => <SearchResult user={user} />)
: null
}
</Row>
);
}
Try putting setState inside the .on(() => ...) just after Object.keys(...). It's an asynchronous call to the server, right? I suppose that setState is called right after the call to the server is made, but before you retrieve any data.

Categories

Resources