-1

This is mainly a question about 'translating' JS to JSX.

I have a react-google-maps map which contains a marker, and a circle around that marker:

The Map component:

export class Map extends Component {
  render() {
    const GoogleMapInstance = withGoogleMap(props => (
      <GoogleMap
        defaultCenter = { { lat: parseFloat(this.props.lat), lng: parseFloat(this.props.lng) } }
        defaultZoom = { 16 }
        defaultOptions={{ styles: mapStyles }}
      >
        <Marker position={{ lat: parseFloat(this.props.lat), lng: parseFloat(this.props.lng) }}/>
        <Circle center={{ lat: parseFloat(this.props.lat), lng: parseFloat(this.props.lng) }} radius={parseFloat(this.props.accuracy)} options={{ fillColor: 'red', strokeColor: 'red' }}/>
      </GoogleMap>
    ))
    return (
      <div>
        <GoogleMapInstance
          containerElement={ <div style={{ height: '600px', width: '100%' }}/> }
          mapElement={ <div style={{ height: '100%' }}/> }
        />
      </div>
    )
  }
}

I would like the map to be zoomed such that the circle takes up about 50% of the map. I understand using the Javascript API, I can just do:

map.fitBounds(circle.getBounds())

But I'm not sure how to integrate that with the JSX that I have...

Alex
  • 2,270
  • 3
  • 33
  • 65

2 Answers2

1

You probably want to do something like this:

export class Map extends Component {
  constructor(props) {
    super(props)
    this.map = React.createRef()
    this.circle = React.createRef()
  }

  componentDidMount() {
    if (this.map && this.circle) {
      const bounds = this.circle.current.getBounds()
      this.map.current.fitBounds(bounds)
    }
  }

  render() {
    const GoogleMapInstance = withGoogleMap(props => (
      <GoogleMap
        defaultCenter = { { lat: parseFloat(this.props.lat), lng: parseFloat(this.props.lng) } }
        defaultZoom = { 16 }
        defaultOptions={{ styles: mapStyles }}
        ref={this.map}
      >
        <Marker position={{ lat: parseFloat(this.props.lat), lng: parseFloat(this.props.lng) }}/>
        <Circle ref={this.circle} center={{ lat: parseFloat(this.props.lat), lng: parseFloat(this.props.lng) }} radius={parseFloat(this.props.accuracy)} options={{ fillColor: 'red', strokeColor: 'red' }}/>
      </GoogleMap>
    ))
    return (
      <div>
        <GoogleMapInstance
          containerElement={ <div style={{ height: '600px', width: '100%' }}/> }
          mapElement={ <div style={{ height: '100%' }}/> }
        />
      </div>
    )
  }
}

The refs create a way to access the GoogleMap and Circle nodes, and the componentDidMount is a lifecycle hook that lets you run the fitBounds call when the Map component is first rendered to the DOM.

coreyward
  • 77,547
  • 20
  • 137
  • 166
  • This looks promising, but for some reason, the 'current' parameter of each ref is always null... – Alex May 21 '19 at 01:52
  • @Alex Sounds like your components either don't have the `ref` attribute set or are not getting rendered. It's been a minute since I've used `componentDidMount`, but I want to say that `render` is called before `componentDidMount` initially. – coreyward May 21 '19 at 02:14
  • Hmm, I copied the code exactly as you wrote it, and the components are definitely rendered (I can see a map with a circle on it on my screen) – Alex May 21 '19 at 02:19
  • It's possible that the components aren't actually doing anything with the ref. You may want to [brush up on the docs](https://reactjs.org/docs/refs-and-the-dom.html) to see what you need. – coreyward May 21 '19 at 03:38
  • I've read that page, and it seems fine, so the only thing I can imagine is that these are functional components, which don't have an instance... – Alex May 21 '19 at 16:51
  • React will usually complain in development if you're passing a ref to a functional component that isn't using forwardRef. – coreyward May 21 '19 at 18:10
  • Oh, it’s not complaining... I have no idea. I’ve tried asking the gitter for the library, but it seems dead – Alex May 21 '19 at 20:04
0

Indeed, you could access native Google Map and Circle objects via Ref and manipulate it. But when it comes to getting map instance, prefer Map idle event over componentDidMount() lifecycle method to guarantee the map is ready, for example:

handleMapIdle() {
   const map = this.map.current; //get Google Map instance
   //...
}

where

 <GoogleMap
        ref={this.map}
        onIdle={this.handleMapIdle}
        ...
      >
      ...
 </GoogleMap>

Here is a modified Map component:

class Map extends Component {
  constructor(props) {
    super(props);
    this.map = React.createRef();
    this.circle = React.createRef();
    this.handleMapIdle = this.handleMapIdle.bind(this);
    this.idleCalled = false;
  }

  handleMapIdle() {
    if (!this.idleCalled) {
      const bounds = this.circle.current.getBounds();
      this.map.current.fitBounds(bounds);
      this.idleCalled = true;
    }
  }

  render() {
    const GoogleMapInstance = withGoogleMap(props => (
      <GoogleMap
        ref={this.map}
        defaultCenter={{
          lat: parseFloat(this.props.lat),
          lng: parseFloat(this.props.lng)
        }}
        defaultZoom={this.props.zoom}
        onIdle={this.handleMapIdle}
      >
        <Marker
          position={{
            lat: parseFloat(this.props.lat),
            lng: parseFloat(this.props.lng)
          }}
        />
        <Circle
          ref={this.circle}
          center={{
            lat: parseFloat(this.props.lat),
            lng: parseFloat(this.props.lng)
          }}
          radius={parseFloat(this.props.accuracy)}
          options={{ fillColor: "red", strokeColor: "red" }}
        />
      </GoogleMap>
    ));
    return (
      <div>
        <GoogleMapInstance
          containerElement={<div style={{ height: "600px", width: "100%" }} />}
          mapElement={<div style={{ height: "100%" }} />}
        />
      </div>
    );
  }
}

Demo

Vadim Gremyachev
  • 57,952
  • 20
  • 129
  • 193