Firebase returning two different outputs - reactjs

I've made a simple class that returns the data downloaded from firebase. The issue is that if I console.log data in the class, it gives data as expected. However, if I import this class anywhere else and try to use it, it returns data as undefined.
Can you explain what's wrong?
My getCollection func in class dbAPI (data is correct)
getCollection(collection) {
dataBase
.collection(collection)
.get()
.then(querySnapshot => {
let data = querySnapshot.docs.map(doc => doc.data())
console.log(data)
return data
})
.catch(function(error) {})
}
The way I try to get data (data is undefined here)
componentDidMount() {
const db = new dbAPI()
let data = db.getCollection("collectionName")
this.setState({ data })}

The issue is that you're trying to return data from a callback to a synchronous function, which is impossible (link). You need to either promisify your getCollection function, or use async/await. If you want to convert your code to use async/await, check out this link. I put an example below. Basically, you need to await for the get request to get the data, then perform your transformation. After performing that transformation, you can return data.
async getCollection(collection) {
const collectionRef = dataBase.collection(collection);
try {
const dataSnapshot = await collectionRef.get();
const data = querySnapshot.docs.map(doc => doc.data());
console.log(data);
return data;
} catch (err) {
console.log(err);
}
}
In your componentDidMount, you must add async/await keywords to the appropriate functions. Async needs to go to the top to mark componentDidMount as async, and you need to await the data from the function call db.getCollection.
async componentDidMount() {
const db = new dbAPI()
const data = await db.getCollection("collectionName")
this.setState({ data })
}

Thanks to you I've managed to do it, really appreaciate your help, my solution:
getCollection:
async getCollection(collection) {
let data = null
await dataBase
.collection(collection)
.get()
.then(querySnapshot => {
data = querySnapshot.docs.map(doc => doc.data())
})
.catch(function(error) {
console.error(`Data fetch failed: \n${error}`)
data = "ERROR"
})
return data}
getting data:
async componentDidMount() {
const db = new Database()
const data = await db.getCollection("collectionName")
this.setState({ ...data })}

Related

Map over Axios Response and append 2nd Axios Response to 1st

I'm creating a movie search React App using axios and the TMDb API. I want to make an axios.get call to search for a movie based on query, then map over the response, and pass the movie.id for each movie into a 2nd axios call to get more details for each movie.
I have tried many approaches, but I'm confused about how I need to return the values, or if I need to use async/await at some point in the code to wait for responses. I don't really know the best way to go about this - there's got to be a simple way to do it in sequence, but the asynchronous nature of the problem makes this difficult for me.
I thought it would be simpler to make my first axios call inside my search function, and then write a second function getDetails(id) that takes a movie id and makes an axios call with it.
Ideally, if I call the getDetails function in a map, get a response, append that response to my movies array, I could easily have an array of movies with appended details to each individual movie. I could then store the entire array in state, and use a single piece of state to manage all my movie data. I know that this process is not synchronous and that axios is promise based, but is there a way to go about this sequentially or am I approaching it all wrong? I know that .push() is not working in this situation, but I have tried other methods and they have always returned undefined, a promise, or an unintended result.
const search = query => {
axios
.get(
`https://api.themoviedb.org/3/search/movie?api_key=${API_KEY}&language=en-US&query=${query}&page=1&include_adult=false`
)
.then(response => {
response.data.results.map(movie => {
const details = getDetails(movie.id);
return movie.push(details);
});
});
};
const getDetails = id => {
axios
.get(
`https://api.themoviedb.org/3/movie/${id}?api_key=${API_KEY}&language=en-US`
)
.then(response => {
return response.data;
});
};
EDIT 1: I've played with the answer by Abanoub Istfanous, and it seemed to return undefined with the added dispatch to update my state. I tweaked it more and the second set of code marked WORKING seems to be displaying some search results, but I cannot render any of the appended data from getDetails to the screen, as they are showing undefined. Am I still doing something incorrectly?
NOT WORKING:
const search = async query => {
dispatch({
type: MOVIE_SEARCH_REQUEST
});
const response = await axios.get(
`https://api.themoviedb.org/3/search/movie?api_key=${API_KEY}&language=en-US&query=${query}&page=1&include_adult=false`
);
const data = Promise.all(
response.data.results.map(async movie => {
movie.details = await getDetails(movie.id);
})
);
dispatch({
type: MOVIE_SEARCH_COMPLETE,
payload: data
});
return data;
};
// getDetails is unchanged...
WORKING:
const search = async query => {
dispatch({
type: MOVIE_SEARCH_REQUEST
});
const response = await axios.get(
`https://api.themoviedb.org/3/search/movie?api_key=${API_KEY}&language=en-US&query=${query}&page=1&include_adult=false`
);
const data = Promise.all(
response.data.results.map(async movie => {
movie.details = await getDetails(movie.id);
})
);
dispatch({
type: MOVIE_SEARCH_COMPLETE,
payload: response.data.results
});
return data;
};
// getDetails is unchanged...
I recommend to use Promise
/*
*#return data
*/
const getDetails = async (id) => {
const response = await axios
.get(
`https://api.themoviedb.org/3/movie/${id}?api_key=${API_KEY}&language=en-US`
)
return response.data
};
then
const search = async (query) => {
const response = await axios
.get(
`https://api.themoviedb.org/3/search/movie?api_key=${API_KEY}&language=en-US&query=${query}&page=1&include_adult=false`
);
const data = Promise.all(response.data.results.map(async movie => {
movie.details = await getDetails(movie.id);
}))
return data
};
The reason why it returns undefined is because Promise.all() will be executed asynchronously on the job queue and will only return the result after all Javascript gets executed and the call stack gets empty. So a simple solution to your code will be placing an await before Promise.all().
const search = async query => {
dispatch({
type: MOVIE_SEARCH_REQUEST
});
const response = await axios.get(
`https://api.themoviedb.org/3/search/movie?api_key=${API_KEY}&language=en-US&query=${query}&page=1&include_adult=false`
);
const data = await Promise.all(
response.data.results.map(async movie => {
movie.details = await getDetails(movie.id);
})
);
dispatch({
type: MOVIE_SEARCH_COMPLETE,
payload: response.data.results
});
return data; //No need to return data since you are dispatching it to redux store
};
You can read more about the asynchronous nature of JS on browsers here:
https://medium.com/#Rahulx1/understanding-event-loop-call-stack-event-job-queue-in-javascript-63dcd2c71ecd

Get data from a promise. Or is it promise at all?

I store some data in IndexedDB and i use npm package localforage for it.
const retrieveData = async () => {
const keys = await localforage.keys()
const data = await keys.map(async (key) => {
const item = await localforage.getItem(key)
return [item.username, item.compamy, item.email, item.lastUpdate]
})
return data
}
Whenever I execute this function, I get a resolved Promise object, which values I cannot extract
async componentDidMount() {
let asyncData = retrieveData()
console.log(asyncData) // Promise object
asyncData = retrieveData().then(values => values)
console.log(asyncData) // Promise object anyways
}
How exactly should I get data from this Promise object?
const retrieveData = async () => {
const keys = await localforage.keys()
// The return value of "keys.map" is an array of promises since
// async automatically returns a Promise behind the scenes.
// Await works on a single promise, not an array of promises,
// so "data" will not contain the actual data.
const data = await keys.map(async (key) => {
const item = await localforage.getItem(key)
return [item.username, item.compamy, item.email, item.lastUpdate]
})
return data
}
Do:
const retrieveData = async () => {
const keys = await localforage.keys()
const data = await Promise.all(keys.map(async (key) => {
const item = await localforage.getItem(key)
return [item.username, item.compamy, item.email, item.lastUpdate]
}));
return data
}
Or use Bluebird's map which works out of the box in this scenario:
// The "then" function does not do anything. It returns values,
// but only does so to the next "then" function. There are no
// further then-functions so the return value is unused.
// "values" is merely a local variable so you won't be able to
// access it anywhere outside the fat arrow function.
// You could move the console log into "then".
asyncData = retrieveData().then(values => values)
// asyncdata is still the unresolved promise object, the "then"
// function has not been run yet (then on the line above will be run when
// all of the awaits in retrieveData have been successfully resolved.
console.log(asyncData)
Do:
async componentDidMount() {
const data = await retrieveData();
console.log(data);
}
Or:
componentDidMount() {
retrieveData().then(values => {
console.log(values);
});
}
you can use for of loop here, and it will be simpler, we dont use await keyword directly in a map iterator, instead we can use promise.all as mentioned in above answer.
const retrieveData = async () => {
const keys = await localforage.keys();
let data;
for (let key of keys) {
const item = await localforage.getItem(key);
data.push([item.username, item.compamy, item.email, item.lastUpdate]);
}
return data;
}
Try using the "await" reserved keyword before your retrieveData() method at componentDidMount(), since it's a promise, an async event, you have to wait until it finishes all of it's inner executions to return some data and go on.
Just like you did at retrieveData() declaration use await before the promise. in detail what you need:
async componentDidMount() {
let asyncData = await retrieveData()
....
}

await aborts the function in a React app. why?

I am trying to fetch data and set the state in React App. While the fetch is successful, as I can see the data in chrome dev tools, the execution stops at await statement in the below code. Only "getting data" is logged. Looks like after fetch statement the function returns, with all the following steps running successfully.
What am I doing wrong??
Any kind of help is much appreciated.
import util from "util";
const fetchProm = util.promisify(fetch)
....
getDataFromDb = async () => {
console.log('getting data')
let result = await fetchProm("http://localhost:3001/getData")
.then(data => {
console.log("then1:",data)
return data.json()
})
.then(res => {
console.log('then2:', res.data)
return { data: res.data }
})
.catch(err => {
return { err: err.data }
});
console.log("result:", result)
this.setState({ data: result.data })
};
you do not need .then callback if you use async-await.
try below sample :
import util from "util";
const fetchProm = util.promisify(fetch)
getDataFromDb = async () => {
console.log('getting data')
let {data} = await fetchProm("http://localhost:3001/getData");
console.log("result:", data)
this.setState({ data })
};
With async/wait you don't need the then. Also you catch errors on the second then and not the first.
Can you try:
let result = await fetchProm("http://localhost:3001/getData")
console.log(result)
and see if it works?
When using async/await, don't forget to handle your exceptions with try/catch
Change your code to:
import util from "util";
getDataFromDb = async () => {
try{
let {data} = await fetchProm("http://localhost:3001/getData");
this.setState({ data })
}
catch(err=> this.setState({ err: err.data }))
};

Get data asynchronously from firebase and set state after the data is reached

I'm using react-native and I want to get data from the firebase realtime database and set state of that data and then only then load the view, I don't want the user to see the data getting pushed and mapped on every load of the chat view.
Here is what I've tried
_getMessages = async () => {
let message_array = [];
await firebase.database().ref('User-Message').child(this.state.fromUser).child(this.state.toUser).on('child_added', async (snapshot) => {
let message_id = await snapshot.key;
let message_ref = await firebase.database().ref('Message').child(message_id).once('value', async (payload) => {
await message_array.push(payload.val())
})
await this.setState({ messages : message_array })
})
}
And in my componentWillMount is simply call the _getMessages() function like this
componentWillMount = async () => {
await this._getMessages();
}
How can I make sure to set the state of messages after getting all the messages from the firebase?
This won't work:
await firebase.database().ref('User-Message').child(this.state.fromUser).child(this.state.toUser).on('child_added', async (snapshot) => {
Firebase's on() method starts actively listening for events. It does not have a clear moment when it's done, so doesn't return a promise. Hence it can't be used with await/async.
My feeling is that you're trying to simply load all user messages, which is easiest to do by using once("value":
let ref = firebase.database().ref('User-Message').child(this.state.fromUser).child(this.state.toUser);
let message_array = await ref.once('value', async (snapshot) => {
let messagePromises = [];
snapshot.forEach(function(userMessageSnapshot) {
messagePromises.push( firebase.database().ref('Message').child(snapshot.key).once('value', async (payload) => {
return payload.val();
})
})
await messageSnapshots = Promise.all(messagePromises);
this.setState({ messages : messageSnapshots })
})
If you want to get realtime updates, you will have to use an on() listener. But that does mean you can't use async/await in the outer listener. It'd look something like this:
let ref = firebase.database().ref('User-Message').child(this.state.fromUser).child(this.state.toUser);
ref.on('value', async (snapshot) => {
let messagePromises = [];
snapshot.forEach(function(userMessageSnapshot) {
messagePromises.push( firebase.database().ref('Message').child(snapshot.key).once('value', async (payload) => {
return payload.val();
})
})
await messageSnapshots = Promise.all(messagePromises);
this.setState({ messages : messageSnapshots })
})

How to turn Firestore query into a Javascript array

I am trying to export a firestore function that performs a query and returns an array containing the objects in that query. I am trying to get data from a subcollection of a document, and get an array of document objects returned to render to the client.
I've tried the below but it's not working (e.g. the object returns blank). I think this has to do with improper handling of promises, but couldn't figure it out on my own. Thanks for your help.
export const getEvents = (id) => {
let events = [];
firestore.collection('users')
.doc(id)
.collection('events')
.get()
.then((snapshot) => {
snapshot.forEach((doc) => events.push(doc));
});
return events;
};
You are correct in identifying this problem is related to the handling of promises. You are returning the events array before it has a chance to be populated, because the promise hasn't resolved yet.
If your environment allows it, I would recommend that you use async/await, because it makes the code much easier to read and understand, like this:
export const getEvents = async (id) => {
let events = [];
const snapshot = await firestore.collection('users')
.doc(id)
.collection('events')
.get()
snapshot.forEach((doc) => events.push(doc));
return events;
};
But if you can't use async/await you can do it with promises. But you need to only resolve the promise after the data is fetched:
const getEvents = (id) => {
return new Promise((resolve, reject) => {
let events = [];
const snapshot = firestore.collection('users')
.doc(id)
.collection('events')
.get()
.then((snapshot) => {
snapshot.forEach((doc) => events.push(doc));
resolve(events); // return the events only after they are fetched
})
.catch(error => {
reject(error);
});
});
};

Resources