I have been trying to place Markers on coordinates on Google Maps for days now, but I run into this error:
Objects are not valid as a React child (found: [object Promise])
So I found out that I can use Promise.all
to solve this issue, but I am having trouble properly doing it. I am not completely sure how and where in my component I should be doing it.
Can anyone help me out?
Here's the code:
import React, { Component } from "react";
import { Map, InfoWindow, Marker, GoogleApiWrapper } from "google-maps-react";
import Navbar from "./Navbar";
import { connect } from "react-redux";
import { getVacations } from "./redux";
import Geocode from "react-geocode";
class GoogleMaps extends Component {
constructor() {
super();
this.state = {
coords: []
}
}
componentDidMount = () => {
this.props.getVacations();
}
render() {
console.log(this.props);
const coordinates = this.props.vacations.map(coordinate => {
const convert = Geocode.fromAddress(coordinate.location);
return convert;
})
Promise.all(coordinates).then(locations => {
const markers = locations.map(place => {
console.log(place);
const lat = place.results[0].geometry.location.lat;
const lng = place.results[0].geometry.location.lng;
return [
<Marker
key={place._id}
position={
{
lat: lat,
lng:lng
}
}
animation={2}
/>
]
})
this.setState({
coords: markers
})
})
return (
<div>
<Navbar />
<Map
google={this.props.google}
zoom={4}
>
{this.state.coords}
</Map>
</div>
)
}
}
const connectVaca = connect(state => ({vacations: state}), {getVacations})(GoogleMaps);
export default GoogleApiWrapper({
apiKey: "API KEY HERE"
})(connectVaca)
Here's my getVacations action and the reducer:
export const getVacations = () => {
return dispatch => {
axios.get("/vacations").then(response => {
dispatch({
type: "GET_VACATIONS",
vacations: response.data
})
}).catch(err => {
console.log(err);
})
}
}
const reducer = (state = [], action) => {
switch(action.type){
case "GET_VACATIONS":
return action.vacations;
default:
return state;
}
}
You shouldn't have asynchronous function calls in your render method. The best place for asynchronous calls is componentDidMount
as per the react documentation.
Place your Promise.all
call into the componentDidMount
function. Whenever your component mounts, this function will be executed. Then, whenever setState
is called to set coords
, your component will rerender with the updated coords
values.
import React, { Component } from "react";
import { Map, InfoWindow, Marker, GoogleApiWrapper } from "google-maps-react";
import Navbar from "./Navbar";
import { connect } from "react-redux";
import { getVacations } from "./redux";
import Geocode from "react-geocode";
class GoogleMaps extends Component {
constructor() {
super();
this.state = {
coords: []
}
}
componentDidMount = () => {
this.props.getVacations();
const coordinates = this.props.vacations.map(coordinate => {
const convert = Geocode.fromAddress(coordinate.location);
return convert;
})
Promise.all(coordinates).then(locations => {
const markers = locations.map(place => {
console.log(place);
const lat = place.results[0].geometry.location.lat;
const lng = place.results[0].geometry.location.lng;
return (
<Marker
key={place._id}
position={
{
lat: lat,
lng:lng
}
}
animation={2}
/>
)
})
this.setState({
coords: markers
})
})
}
render() {
return (
<div>
<Navbar />
<Map
google={this.props.google}
zoom={4}
>
{this.state.coords}
</Map>
</div>
)
}
}
const connectVaca = connect(state => ({vacations: state}), {getVacations})(GoogleMaps);
export default GoogleApiWrapper({
apiKey: "API KEY HERE"
})(connectVaca)
Edit: Since loadVacations
is a thunk, you need to pass it to dispatch
. Change your mapDispatchToProps
function passed to connect
like so:
const connectVaca = connect(
state => ({vacations: state}),
dispatch => {
return { getVacations: () => dispatch(getVacations()) }
}
)(GoogleMaps);
Edit2: It has to do with the fact that react runs an intial render()
call once the page is loaded. Then, once your component is mounted, componentDidMount
gets called. In componentDidMount
, you run an asynchronous function getVacations
. Since you're not waiting for your API call in getVacations
to finish, the marker creation happens without any vacations loaded yet. Whenever you visit a new tab and go back to your application, render
happens again with the loaded vacations from the previous API call which is why they show up.
Try returning a promise from your action getVacations
. Then, whenever you call getVacations
in componentDidMount
, chain a then
call to getVacations
. Inside then
is where you should place your coordinates creation + Promise.all
call. Then I believe your code will work.
export const getVacations = () => {
return dispatch => {
// returning axios.get will return a promise you can chain a then call on
return axios.get("/vacations").then(response => {
dispatch({
type: "GET_VACATIONS",
vacations: response.data
})
}).catch(err => {
console.log(err);
})
}
}
Then in your componentDidMount
function:
componentDidMount = () => {
this.props.getVacations().then(() => {
// Place coordinates, your Promise.all call, and your setState call here
});
}