React-google-maps: How can I "edit" Polygons and update state to match?

Created on 8 Jun 2016  路  12Comments  路  Source: tomchentw/react-google-maps

If I allow editable polygons, I can listen for onMouseup and get back some "stuff" but I don't see anything which tells me which polygon I've just updated.

I have this.state.polygons as an array of polygon objects which contain paths...

[{"id":0,"editable":true,"path":[{"lat":38.25764366831546,"lng":-85.7516598701477},{"lat":38.25656530505969,"lng":-85.75185298919678},{"lat":38.25808174881664,"lng":-85.75474977493286}],"area":16623.009493033773,"options":{"fillColor":"#caebba"}}]

What I'm looking for is _(I think)_ a way to update state from what happens to be drawn/edited... either for all Polygons, or for the edited one... but I can't seem to find a listener which tells me which one it is...

NOTE - I know that if I draw the polygons with drawing tools, I have events onOverlaycomplete and onPolygoncomplete to use and link into w/ more options (raw google map objects) but that doesn't help me when I create with <Polygon />

Most helpful comment

Thank you @iamgutz, that was very helpful. Here is a slightly more up to date way of doing it.

import React, { PureComponent } from 'react';
import propTypes from 'prop-types';
import { Polygon } from 'react-google-maps';

export default class myPolygon extends PureComponent {
  static propTypes = {
    id: propTypes.string.isRequired,
    paths: propTypes.arrayOf(
      propTypes.shape({
        lat: propTypes.number.isRequired,
        lng: propTypes.number.isRequired
      }).isRequired
    ),
    onChangeStart: propTypes.func,
    onChangeEnd: propTypes.func,
    onChangeSet: propTypes.func,
    onChangeInsert: propTypes.func,
    onChangeRemove: propTypes.func,
  }

  static defaultProps = {
    onChangeStart: () => null,
    onChangeEnd: () => null,
    onChangeSet: () => null,
    onChangeInsert: () => null,
    onChangeRemove: () => null,
  }

  shouldComponentUpdate() {
    return false;
  }

  componentDidMount() {
    const addListener = (type, func) => google.maps.event.addListener(this.__polygon, type, func);

    addListener('set_at', position => this.props.onChangeSet(this.onChange(position)));
    addListener('insert_at', position => this.props.onChangeInsert(this.onChange(position)));
    addListener('remove_at', position => this.props.onChangeRemove(this.onChange(position)));
  }

  onChange = position => ({
    coordinate: {
      lat: this.__polygon.b[position].lat(),
      lng: this.__polygon.b[position].lng(),
    },
    id: this.props.id,
    position,
  });  

  onRemove = position => ({
    id: this.props.id,
    position,
  });

  __ref = ref => this.__polygon = ref && ref.getPath();

  render() {
    const { paths, onChangeStart, onChangeEnd } = this.props;

    return <Polygon
      ref={this.__ref}
      paths={paths}
      options={{
        editable: true,
      }}
      onMouseDown={onChangeStart}
      onTouchStart={onChangeStart}
      onMouseUp={onChangeEnd}
      onTouchEnd={onChangeEnd}
    />;
  }
}

All 12 comments

@zeroasterisk You need to use refs for the polygon that triggered the event.
For Example, something like this:
_assign a ref name to each polygon_
const refs = 'polygon' + index
_set the polygon component adding the ref name and on the mousedown event pass a function that will receive the ref name as well._
<Polygon ref={refs} center={polygon.center} paths={polygon.paths} options={polygon.options} onMousedown={() => { this._onMousedown(refs) }} editable />

_onMousedown = (ref) => {
_get the reference to the googlemap and to the specific polygon (with the ref param)_
const polygon = this.refs['googleMap'].refs[ref].getPath()
_add event listeners for the polygon changes and pass the polygon as parameter to the function you need_
google.maps.event.addListener(polygon, 'set_at', () => { this._getPolygonNewPaths(polygon) })
google.maps.event.addListener(polygon, 'insert_at', () => { this._getPolygonNewPaths(polygon) })
google.maps.event.addListener(polygon, 'remove_at', () => { this._getPolygonNewPaths(polygon) })
}

_on this function you receive the polygon and then you can iterate each of its paths to get the lat and lng values_
_getPolygonNewPaths = (polygon) => {
let polygonPaths = []
polygon.getArray().forEach((path, index) => {
const line = {
lat: path.lat(),
lng: path.lng()
}
polygonPaths.push(line)
})
}

this adds the listener on every mousedown

No, on mouse down it sends the ref of the polygon element that triggered it, with the ref then you can get the polygon position etc.

Try using polygon.getPath() method with ref?

Also, 6.0.0 is released on npm beta tag now. See the changelog here. We also have a new demo page. Feel free to try it:
https://tomchentw.github.io/react-google-maps/

Please refer to Getting Help section in the README (or #469).

Thank you @iamgutz, that was very helpful. Here is a slightly more up to date way of doing it.

import React, { PureComponent } from 'react';
import propTypes from 'prop-types';
import { Polygon } from 'react-google-maps';

export default class myPolygon extends PureComponent {
  static propTypes = {
    id: propTypes.string.isRequired,
    paths: propTypes.arrayOf(
      propTypes.shape({
        lat: propTypes.number.isRequired,
        lng: propTypes.number.isRequired
      }).isRequired
    ),
    onChangeStart: propTypes.func,
    onChangeEnd: propTypes.func,
    onChangeSet: propTypes.func,
    onChangeInsert: propTypes.func,
    onChangeRemove: propTypes.func,
  }

  static defaultProps = {
    onChangeStart: () => null,
    onChangeEnd: () => null,
    onChangeSet: () => null,
    onChangeInsert: () => null,
    onChangeRemove: () => null,
  }

  shouldComponentUpdate() {
    return false;
  }

  componentDidMount() {
    const addListener = (type, func) => google.maps.event.addListener(this.__polygon, type, func);

    addListener('set_at', position => this.props.onChangeSet(this.onChange(position)));
    addListener('insert_at', position => this.props.onChangeInsert(this.onChange(position)));
    addListener('remove_at', position => this.props.onChangeRemove(this.onChange(position)));
  }

  onChange = position => ({
    coordinate: {
      lat: this.__polygon.b[position].lat(),
      lng: this.__polygon.b[position].lng(),
    },
    id: this.props.id,
    position,
  });  

  onRemove = position => ({
    id: this.props.id,
    position,
  });

  __ref = ref => this.__polygon = ref && ref.getPath();

  render() {
    const { paths, onChangeStart, onChangeEnd } = this.props;

    return <Polygon
      ref={this.__ref}
      paths={paths}
      options={{
        editable: true,
      }}
      onMouseDown={onChangeStart}
      onTouchStart={onChangeStart}
      onMouseUp={onChangeEnd}
      onTouchEnd={onChangeEnd}
    />;
  }
}

Hi @zeroasterisk ! Did you manage to resolve this? I am having the same issue right now.

Hi @davidjbradshaw ! Thanks for your answer. I have a question with regards to the update of props.path.

What I'm doing:
I compare this.props.path with nextProps.path in shouldComponentUpdate: if the new path is different from the previous one, shouldComponentUpdate will return true, otherwise, it returns false. The following is a code snippet:

screen shot 2017-11-21 at 5 40 39 pm

Reason for doing this:
I want to update my polygon based on the new props.path by rerendering it but because shouldComponentUpdate always return false, the polygon won't be rendered again.

What is the problem:
This works for me ONLY the first time the user edit the polygon. It seems to me that when shouldComponentUpdate returns false after it returns true the first time, set_at, insert_at and remove_at aren't called.

I'm wondering what did I do wrong that causes this problem? Any help would be appreciated!

Hi @davidjbradshaw , I find the problem why ONLY the first time set_at is called. It's because of the way the ref works in react component and also because of the place getPath() is called.
React component's ref takes a callback function that's executed only after the component is mounted and unmounted when it's defined as a bound function, but not when the component is updated. So because getPath() is called in this callback of ref, the event listener of set_at is only added to the initial path Polygon is mounted, but not the path after it's updated. That's why only the set_at is called when shouldComponentUpdate returns true and also the reason set_at gets called every time when shouldComponentUpdate returns false, which is not what I wanted in my case.

Solution:
Based on what I said, I edit the code a bit in order to update Polygon everytime props.path changes:

screen shot 2017-11-23 at 10 34 13 am

Hi,
I don't know if this exact problem was answered or not but I also have problem with updating Polygon while user clicks on map.
I add the location to an array which is passed to Polygon. Like this:

<Map onClick={(event) => {this.props.updatePaths({ lat: event.latLng.lat(), lng: event.latLng.lng() }); }}> <Polygon path={this.props.paths}/> </Map>

The props updates properly but Polygon doesn't. I first had the path as an state in same component but I felt it would work if I update it in parent component and pass it as props, but it didn't fix.

I read this forum and some others but I couldn't find out the answer.

Thank you

if you add a ref to the polygon, you can call .Path() and .Paths from, the ref.
now if a user moves a vertex on the polygon, the handler of mouse up will be called and at that exact moment polygon.getPath() will return the old value. The problem is that the event is called before applying the new coordinates , so if you write the event handler like this

onMouseUpHandler = () => {
   window.setTimeout(() => {
    const newPath = polygon.getPath()
  }, 0);
}

newPath will be the new coordinates, where polygon is reference to the <Polygon /> component
@tomchentw please add this note to the documents. I know that the problem is probably in google's apis, but mentioning this will be very helpful

@davidjbradshaw , @liutongchen :
could you post an example on codesandbox?

Was this page helpful?
0 / 5 - 0 ratings

Related issues

craigcartmell picture craigcartmell  路  4Comments

madbean picture madbean  路  3Comments

shrimpy picture shrimpy  路  3Comments

bansalvks picture bansalvks  路  3Comments

PaulieScanlon picture PaulieScanlon  路  3Comments