L.Rencoret
L.Rencoret

Reputation: 155

react-google-maps Multiple markers and Info Window

I need to do create a Info Window for each Marker I have in my map. I can see the markers on the map, but when I click over the marker to get the InfoWindow, I get this error TypeError: Cannot read property 'isOpen' of undefined, referencing to this.setState({isOpen: !this.state.isOpen}) in the onToggleOpen.

Thanks on behalf

componentWillMount() {
    this.setState({ markers: [], isOpen: false })
  }

componentDidMount() {
    let list = { restaurants : []}
    database.ref('..').on('value', .. => {
            ...
            })
            this.setState({ markers:  list.restaurants});
        })

}

onToggleOpen(){
    this.setState({isOpen: !this.state.isOpen})
}


render() { 
    const MapWithAMarker = compose(
    withProps({
        googleMapURL: ...,
        loadingElement: ...,
        containerElement: ...,
        mapElement: ...
      }),
      withScriptjs,
      withGoogleMap
    )(props =>
      <GoogleMap
        defaultZoom={17}
        defaultCenter={{ lat: ..., lng: ... }}
      >
          {props.markers.map(marker => (
            <Marker
              key={marker.name}
              position={{ lat: marker.latitude, lng: marker.longitude }}
              onClick={this.onToggleOpen}
            >
            {this.state.isOpen && <InfoWindow onCloseClick={this.onToggleOpen}> marker.key </InfoWindow>}
            </Marker>
          ))}


      </GoogleMap>
    );
    return (
        <section id="mapa">
            <div class= "container">
                <div class="row">
                    <div class="col-md-12 text-left">
                        <h2 class="section-heading">MAPA</h2>

                        <MapWithAMarker markers={this.state.markers}/>

                    </div>
                </div>
                <div class="row text-center">

                </div>
            </div>
        </section>
    ) 
}

EDIT: Updated code:

export default class Mapa extends Component {
  constructor(props) {
    super(props)
    this.state = {
      markers: [],
      isOpen: false
    }
  }

componentDidMount() {
    let list = { restaurants : []}
    database.ref('..').on('value', restaurants => {
            restaurants.forEach( restaurant => {
                    list.restaurants.push({'name': restaurant.key,
                            'longitude': restaurant.val().lng,
                            'latitude': restaurant.val().lat}

                        )
            })
            this.setState({ markers:  list.restaurants });
        })

}

onToggleOpen = () => {
        this.setState({isOpen: !this.state.isOpen})
    }

render() { 

    const MapWithAMarker = compose(
    withProps({
        googleMapURL: "https://maps.googleapis.com/maps/api/js?key=...&v=3.exp&libraries=geometry,drawing,places",
        loadingElement: <div style={{ height: `100%` }} />,
        containerElement: <div style={{ height: `400px` }} />,
        mapElement: <div style={{ height: `100%` }} />
      }),
      withScriptjs,
      withGoogleMap
    )(props =>
      <GoogleMap
        defaultZoom={17}
        defaultCenter={{ lat: .., lng: ... }}
      >
          {props.markers.map(marker => (
            <Marker
              key={marker.name}
              position={{ lat: marker.latitude, lng: marker.longitude }}
              onClick={this.onToggleOpen}
            >
            {this.state.isOpen && <InfoWindow onCloseClick={this.onToggleOpen}> marker.key </InfoWindow>}
            </Marker>
          ))}

      </GoogleMap>
    );
    return (
        <section id="mapa">
            <div class= "container">
            <h3 class="info-title"><span class="info-title-span">Tasty and Convenient.</span></h3>
                <div class="row">
                    <div class="col-md-12 text-left">


                        <MapWithAMarker markers={this.state.markers}/>

                    </div>
                </div>
                <div class="row text-center">

                </div>
            </div>
        </section>
    ) 
}

Upvotes: 2

Views: 10125

Answers (2)

Fabien Sartori
Fabien Sartori

Reputation: 235

I did this in the main wrapper

withStateHandlers(() => ({
    isOpen: {},
  }), {
    onToggleOpen: ({ isOpen }) => (id) => ({
      isOpen: {
        ...isOpen,
        [id]:isOpen[id]==undefined?true:!isOpen[id]
      },
    })
  }),

And this on Marker component

{props.servicePoints.map((x) =>
        <Marker
          onClick={()=>{
            props.onToggleOpen(x.id);
          }}
          animation={google.maps.Animation.DROP}
          key={x.id}
          icon={"favicon.ico"}
          position={{ lat: x.latitude, lng: x.longitude }}
        >
          {props.isOpen[x.id]===true &&
            <InfoWindow
              key={x.id}
              options={{ closeBoxURL: ``, enableEventPropagation: true }}
              onCloseClick={()=>{
                props.onToggleOpen(x.id);
              }}>
              <div>
                <label>Nome:{x.name}</label><br></br>
                <label>Descrizione:{x.description}</label><br></br>
                <label>Indirizzo:{x.address}</label>
              </div>
            </InfoWindow>}}
         </Marker>
      )}

All is working fine. So if I click one Marker, only one InfoWindow appear.

Upvotes: 0

Arman Charan
Arman Charan

Reputation: 5797

The error indicates that this.state == undefined within the scope of where you are trying to retrieve this.state.isOpen.

This is because your this.onToggleOpen() function's scope is not properly bound to your component.

Try refactoring it to use an arrow function like so:

onToggleOpen = () => {
    this.setState({isOpen: !this.state.isOpen})
}

Side note:

Best practice to declare initial this.state is to do so within your component's constructor lifecycle method like so:

constructor(props) {
  super(props)
  this.state = {
    markers: [],
    isOpen: false
  }
}

Also, should this.state.isOpen be a unique property for of each of your markers instead of a blanket value for all of them? So that you can open each marker individually, instead of all at once.

Example Solution:

// Dependencies.
import React from 'react'
// GoogleMap, etc ..

// Map.
export default class Map extends React.Component {

  // State.
  state = {markers: []}

  // Render.
  render() {

    // Map With A Marker.
    const MapWithAMarker = compose(
      withProps({
        googleMapURL: "https://maps.googleapis.com/maps/api/js?key=...&v=3.exp&libraries=geometry,drawing,places",
        loadingElement: <div style={{height: `100%`}}/>,
        containerElement: <div style={{height: `400px`}}/>,
        mapElement: <div style={{height: `100%`}}/>
      }),
      withScriptjs,
      withGoogleMap
    )(props =>
      <GoogleMap defaultZoom={props.zoom} defaultCenter={{lat: props.lat, lng: props.lng}}>
        {props.markers.map(props => <RestaurantMarker key={props.name} {...props}/>)}
      </GoogleMap>
    )

    // Return Map.
    return (
      <section id="map">
        <div class= "container">
        <h3 class="info-title"><span class="info-title-span">Tasty and Convenient.</span></h3>
          <div class="row">
            <div class="col-md-12 text-left">
              <MapWithAMarker 
                markers={this.state.markers}
                lat={37.7749}
                lng={122.4194}
                zoom={17}
              />
            </div>
          </div>
          <div class="row text-center">
            Text
          </div>
        </div>
      </section>
    )

  }

  // Did Mount.
  componentDidMount() {

    // Download Restaurants.
    database.ref('..').on('value', restaurants => {

      // Restaurants Placeholder.
      let markers = []

      // Map Restaurants To List.
      restaurants.forEach(restaurant => {
        markers.push({
          'name': restaurant.key,
          'longitude': restaurant.val().lng,
          'latitude': restaurant.val().lat
        })
      })

      // Update State.
      this.setState({markers})

    })

  }

}


// RestaurantMarker.
class RestaurantMarker extends React.Component {

  // State.
  state = {open: false}

  // Render.
  render() {

    // Extract Props.
    const {name, latitude, longitude} = this.props

    // Return Restaurant Marker Component.
    return (
      <Marker key={name} position={{ lat: latitude, lng: longitude }}>
        {this.state.open && (
          <InfoWindow onClick={() => this.setState(state => ({open: !state.open}))}> {name} </InfoWindow>
        )}
      </Marker>
    )

  }

}

Upvotes: 5

Related Questions