简体   繁体   中英

Using Promise.all to place Markers on Google Maps - Objects are not valid as a React child (found: [object Promise])

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 
    });
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM