react-google-maps icon indicating copy to clipboard operation
react-google-maps copied to clipboard

How to add Custom Controls?

Open SyedSaifAli opened this issue 6 years ago • 19 comments

Hi, I am using react-google maps, its great. I am showing multiple markers in the map Now I want a custom control i.e a drop down inside the map, in which If I can select a particular marker and it will only show that marker.

The only problem I am facing is I am unable to show that drop down on the map.

Is there any way to show it on the map?

Any help will be appreciated.

SyedSaifAli avatar Apr 10 '18 13:04 SyedSaifAli

@tomchentw Is there anything which can be done for this? Can we create our own custom control just like SearchBox component?

SyedSaifAli avatar Apr 11 '18 05:04 SyedSaifAli

@SyedSaifAli We can create custom controls to render on the map,

  1. First we need to create a div and add it into Google Maps Controls, something like below

this.map.controls[google.maps.ControlPosition.BOTTOM_RIGHT].push( showhideControlDiv //div container );

  1. Now you can render any component into that Div, with ReactDom.render(<ComponentName/>, document.getElementById("showhideControlDiv "))

In the above way we are able to show dropdowns and legends on the Map

RamYadlapalli avatar Apr 11 '18 06:04 RamYadlapalli

@RamYadlapalli Can you provide an example of how to implement it?

SyedSaifAli avatar Apr 11 '18 08:04 SyedSaifAli

I hope this helps. https://gist.github.com/jgimbel/6a36d60e28aaf453d0093ddc47f36533

jgimbel avatar Apr 19 '18 15:04 jgimbel

This is not totally related to this post, but I am having issues where I need to remove a custom control from google map object.

did a repo for this. https://github.com/github0013/react-google-maps-custom-control-removal

@jgimbel https://gist.github.com/jgimbel/6a36d60e28aaf453d0093ddc47f36533#file-mapcontrol-js-L15 This line tries to remove the pushed element, and I am assuming this index is from the push return value. "this.divIndex" assignment is missing from the gist.

I did like this https://github.com/github0013/react-google-maps-custom-control-removal/blob/master/src/MapControl.tsx#L20

But I am not sure if it's right since

https://developers.google.com/maps/documentation/javascript/reference/3.exp/map this.map.controls[this.props.position] is MVCArray<Node> maps_ _ google_maps_javascript_api _ _google_developers

https://developers.google.com/maps/documentation/javascript/reference/3.exp/event#MVCObject and the return value from the push method is event_system_ _ google_maps_javascript_api _ _google_developers It's only the length

Problem

My problem is when to actually unmount the component https://github.com/github0013/react-google-maps-custom-control-removal/blob/master/src/MapContainer.tsx#L21

this error hapens. 0e02ec913ef91e384b8a79e241dd04b1

so how did you actuall get this.divIndex value, and remove the control element safely?

github0013 avatar Apr 23 '18 02:04 github0013

fixed

https://github.com/github0013/react-google-maps-custom-control-removal/commit/1c5467e1667254dd69285b52d1533cf73992951a

I guess the this.divIndex can be obtained like this. https://github.com/github0013/react-google-maps-custom-control-removal/commit/1c5467e1667254dd69285b52d1533cf73992951a#diff-1728495de7abffb586b4d385b72e588fR20

and for the exception, I wrapped the MapControl component with a div.

ref: https://github.com/facebook/react/issues/6802

github0013 avatar Apr 23 '18 02:04 github0013

So... I am not unmounting my custom controls, so I wrote it initially without the removeAt. Then I remembered that when I wrote custom controls for leaflet I was informed that the controls were not getting removed on unmount. So I added unmounting to the gist and I forgot to set divIndex like you mentioned. Long story short, now that react 16 has portals unmounting is less necessary than it was back when I wrote this kind of thing for leaflet. Without calling removeAt there is a small memory leak because the mounting div is never cleaned up. If the returned value of push is the length of the array, it sounds like you can just subtract one from it to get the correct index. Haven't tested that though.

jgimbel avatar Apr 23 '18 02:04 jgimbel

This is how I did, a little adjust base on @jgimbel 's example.

Map.js

class Map extends React.Component {
  componentDidMount() {
    // add <CustomControl /> to mapControl
    this.map.context[MAP].controls[google.maps.ControlPosition.RIGHT_BOTTOM].push(this.control);
  }

   render() {
     return (
        <GoogleMap ref={(ref) => { this.map = ref; }}>
            <CustomControl  ref={(ref) => { this.control = ref; }} />
             ...
        </GoogleMap>
     )
   }
}

CustomControl.js

export default () => (
  <div>
    // something want to display on control
  </div>
)

oahehc avatar Jun 11 '18 10:06 oahehc

@jgimbel @SyedSaifAli I was getting the same TypeError while unmounting the the map controls. this is how I fixed it : https://gist.github.com/sakhisheikh/87dd587ec126189e95396c380d49298f

@jgimbel I have updated the gist. You do a PR now.

sakhisheikh avatar Aug 05 '18 20:08 sakhisheikh

I just did this using the constructor method instead of componentWillMount because it is unsafe now and struggled a little to make it work because I was not passing the context to the super call:

constructor(props, context) {
  super(props, context);
  const { controlPosition } = props;

  this.map = this.context[MAP];
  this.control = document.createElement('div');
  this.map.controls[controlPosition].push(this.control);
}

probably it is something basic, but better leave it here for future visitors.

piedrahitapablo avatar Nov 16 '18 21:11 piedrahitapablo

So I ran into an error when unmounting multiple custom controls with the solution above. removeAt will make this.divIndex of other custom controls component no longer correct. Here is how I fixed it for future visitors and future me:

componentWillUnmount() {
    const controlArray = this.map.controls[this.props.position].getArray()
    for (let index in controlArray) {
        if (controlArray[index] === this.controlDiv) {
            this.map.controls[this.props.position].removeAt(index);
            break;
        }
    }
}

longhungn avatar Dec 10 '18 01:12 longhungn

This should be added to the library! https://gist.github.com/jgimbel/6a36d60e28aaf453d0093ddc47f36533#gistcomment-2563894

markmssd avatar Dec 26 '18 08:12 markmssd

@markmssd this library is unmaintained about a year, please look at ‘react-google-maps-api’ as replacement

JustFly1984 avatar Dec 26 '18 16:12 JustFly1984

@JustFly1984 Can you add link of that library? react-google-maps-api

SyedSaifAli avatar Dec 28 '18 07:12 SyedSaifAli

@SyedSaifAli https://www.npmjs.com/package/react-google-maps-api

JustFly1984 avatar Dec 28 '18 17:12 JustFly1984

@JustFly1984 Hey, Did you add some changes around DrawingManager in your forked library? Or do you of anyone else who managed to achieve DrawingManager public API's which are not exposed in this package?

SyedSaifAli avatar Jan 24 '19 08:01 SyedSaifAli

For any other confused travelers, react-google-maps uses React's Legacy Context API instead of the current one. This was relevant for me because I was trying to get this done in a functional component. My completed component (using React 16.8 Hooks, but using the deprecated-in-16.3 Legacy Context) looks like this:

import React, { useEffect } from 'react';
import { createPortal } from 'react-dom';
import { MAP } from 'react-google-maps/lib/constants';
import PropTypes from 'prop-types';

export default function CustomDrawingManagerControl(
  { position = window.google.maps.ControlPosition.TOP_LEFT, children },
  context
) {
  const map = context[MAP];

  const controlDiv = document.createElement('div');

  useEffect(() => {
    const controls = map.controls[position];
    const index = controls.length;
    controls.push(controlDiv);
    return () => {
      controls.removeAt(index);
    };
  });

  return createPortal(
    <div style={{ marginLeft: 16, marginTop: 16 }}>{children}</div>,
    controlDiv
  );
}

CustomDrawingManagerControl.contextTypes = {
  [MAP]: PropTypes.object,
};

To be used like this:

<GoogleMap {...props}>
    <CustomMapControl position={google.maps.ControlPosition.BOTTOM_CENTER}>
      <div>That was easy</div>
    </CustomMapControl>
</GoogleMap>

a8t avatar Apr 22 '19 16:04 a8t

@a8t I would advise you to look at @react-google-maps/api. We have rewritten it to typescript and new context API, as well as hooks API. https://github.com/JustFly1984/react-google-maps-api/tree/master/packages/react-google-maps-api

JustFly1984 avatar Apr 22 '19 18:04 JustFly1984

`<GoogleMap

    id="map"
    mapContainerStyle={mapContainerStyle}
    zoom={8}
    center={props.center}
    options={{
      mapTypeControlOptions: {
        style: window.google.maps.MapTypeControlStyle.HORIZONTAL_BAR,
        position: window.google.maps.ControlPosition.TOP_CENTER
      },
      zoomControlOptions: {
        position: window.google.maps.ControlPosition.LEFT_CENTER
      },
      streetViewControlOptions: {
        position: window.google.maps.ControlPosition.LEFT_TOP
      }
    }}
    onClick={onMapClick}
    onLoad={onMapLoad}
  >`

asharuDheen-code avatar Jun 01 '22 09:06 asharuDheen-code