I am trying to build a find nearby restaurants app in ReactJS and I am having a problem with displaying array data on the first run of this command below.
What happens is that the first time I fetch data with axios it only load location. The second time I fetch data it also renders the array data on the interface.
I think the problem is with the order of the command I run in this function and on the first run it tries to fetch undefined lat & lng, then after the second time we run it in the same session it renders based on what lat & lng was fetches with the previous run of the function.
Here's the code:
currentLocationOnClick = async () => {
let { lat, lng } = this.state;
const URL = `https://maps.googleapis.com/maps/api/place/nearbysearch/json?location=${lat},${lng}&type=restaurant&radius=${5 *
1000}&key=AIzaSyBpd_v1C8RFh0D39Al97ANZ-eJLO3zrKAQ`;
navigator.geolocation.getCurrentPosition(
position => {
this.setState({ lat: position.coords.latitude });
this.setState({ lng: position.coords.longitude });
},
error => {
console.log('Error getting location');
}
);
let places;
try {
const response = await axios.get(URL);
console.log(response.data);
places = response.data.results;
} catch (error) {
console.log(error.message);
}
this.setState({ places });
};
navigator.geolocation.getCurrentPosition is an async function, meaning the function call does not block until it finished.
one way of fixing this would be to put your axios call in position callback.
For example, something like this:
async position => {
const URL = `https://maps.googleapis.com/maps/api/place/nearbysearch/json?location=${position.coords.latitude},${position.coords.longitude}&type=restaurant&radius=${5 *
1000}&key=AIzaSyBpd_v1C8RFh0D39Al97ANZ-eJLO3zrKAQ`;
const response = await axios.get(URL);
console.log(response.data);
places = response.data.results;
this.setState({ places });
},
Another nicer, solution would be to wrap navigator.geolocation.getCurrentPosition so it's an awaitable function.
something like this:
function getCurrentPosition()
{
return new Promise(resolve => navigator.geolocation.getCurrentPosition(position => resolve(position)))
}
Then you could use it something like this:
let position = await getCurrentPosition();
const URL = `https://maps.googleapis.com/maps/api/place/nearbysearch/json?location=${position.coords.latitude},${position.coords.longitude}&type=restaurant&radius=${5 *
1000}&key=AIzaSyBpd_v1C8RFh0D39Al97ANZ-eJLO3zrKAQ`;
const response = await axios.get(URL);
Related
I cannot understand why the .map() function is somehow not being called in this function. I'm trying to call my API asynchronously within the componentDidMount() hook, but it seems that the array method never gets called.
async componentDidMount() {
try {
const response = await fetch(url);
const data = await response.json();
console.log(`Returned: ${data.zipCodes.length} results`);
console.log(data.zipCodes[0]);
const geoPoints = data.zipCodes;
geoPoints
.filter(geoPoint => geoPoint.Recipient_Postal_Code !== "")
.map(function (geoPoint) {
const location = zipToLocation.find(element => element.fields.zip === geoPoint.Recipient_Postal_Code);
if (!location){
return;
}
if(!location.hasOwnProperty('fields')) {
return;
}
const lat = location.fields.latitude;
const lng = location.fields.longitude;
const result = {
lat: lat,
lng: lng,
trackingNumber: geoPoint.Shipment_Tracking_Number
}
console.log(result); //OK!
return result;
});
console.log(`Mapped ${geoPoints.length} geoPoints`);
console.log(geoPoints); //same data as data.zipCodes ??
this.setState({data: geoPoints})
} catch(error) {
console.log(error);
}
}
Is is somehow being disrupted by React?
.map doesn't edit the input array, it returns a new array that is created from the old one.
You want to put:
const geoPoints = data.zipCodes
.filter(...)
.map(...);
I am trying to upload an image to Firebase storage. The problem is that since the user has not signed up yet, I don't have their uid.
I depend on onAuthStateChanged to get the user id and upload an image to their bucket, but so far it hasn't turned out well.
const { userObject } = useContext(Context) //trying to get the uid from here
onSubmit={(values, { setSubmitting }) => {
async function writeToFirebase() {
firebaseService.auth.createUserWithEmailAndPassword(values.email, values.password)
await firebaseService.firestore.collection('businessesPendingAdminApproval').add(values)
}
writeToFirebase()
async function sendToFirebaseImageBucket(photo, uid) {
const businessRef = await firebaseService.firestore.doc(
`businessesPendingAdminApproval/${uid}`,
)
firebaseService.storage
.ref()
.child('businesses')
.child(uid)
.child('avatar-image')
.put(photo)
.then(response => response.ref.getDownloadURL())
.then(photoURL => businessRef.update({ avatarImage: photoURL })) //try to update avatarImage
}
const uid = userObject.uid //undefined, can't get uid
sendToFirebaseImageBucket(values.avatarImage, uid) //uid gets passed as undefined
}}>
The way I am setting the userObject which is where I'm trying to get the uid from.
Setting the userObject eventually works but maybe not fast enought for me to be able to pass it to a function (as in the code above).
useEffect(() => {
firebaseService.auth.onAuthStateChanged(async function (userAuth) {
if (userAuth) {
const user = await firebaseService.createUserProfileDocument(userAuth)
setUserObject(user) //set userObject which has an uid field.
} else {
console.log('no one signed in')
}
})
}, [])
Just add your image to cloud storage right after you have logged in and was able to get uid. the following code can help you, it works for me as well. put the following code inside useEffect.
const unsubscribe = auth().onAuthStateChanged(user => {
if (user.uid){
const ref = storage.ref(`images/${user.uid}`);
const task = ref.putFile(_image, { contentType: 'image/jpeg' });
task.on(firebase.storage.TaskEvent.STATE_CHANGED, snap => {
setState({ type: 'cents', value: snap.bytesTransferred / snap.totalBytes * 100 });
}, err => { console.log('Error in help:persisAppState: ', err) }, async () => {
const image = await ref.getDownloadURL();
if (image) await db.collection("imagelinks").doc(user.id).set({ image });
});
}
});
im trying to make a request to google api but returns me network error. If i put the url in the browser, brings me the information correctly.I tryed to formate the request without success. The google places search works correctly too.
export const fetch_information = (skip, limit, filter) => async (dispatch) => {
try {
var url = `https://maps.googleapis.com/maps/api/place/details/json?place_id=ChIJk0aJYPbk3JQRLpKN20Jecko&fields=name,rating,formatted_phone_number&key=MyKey`;
const {data} = await axios.get(url)
console.log(data)
} catch (error) {
console.log(error.message)
}
}
and
export const fetch_information = (skip, limit, filter) => async (dispatch) => {
try {
var url = `https://maps.googleapis.com/maps/api/place/details/json?`;
let config = {
params: {
place_id: 'ChIJk0aJYPbk3JQRLpKN20Jecko',
key: 'myKey',
},
}
const {data} = await axios.get(url, config)
console.log(data)
} catch (error) {
console.log(error.message)
}
}
I think that the request looks a bit messy. I'm under the impression that you are trying to pass results to a redux store. Let's see if we can clean this up a bit.
export const fetch_information = async () => dispatch => {
const req = await axios.get("https://maps.googleapis.com/maps/api/place/details/json?place_id=ChIJk0aJYPbk3JQRLpKN20Jecko&fields=name,rating,formatted_phone_number&key=MyKey");
const data = await req.json();
return data;
//or, for your purpose...
console.log(data);
//can also dispatch for store
}
I didn't see anything you were passing as necessary for this.
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
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 })}