fullstackreact / google-maps-react

Companion code to the "How to Write a Google Maps React Component" Tutorial
https://www.fullstackreact.com/articles/how-to-write-a-google-maps-react-component/
MIT License
1.63k stars 819 forks source link

Add domRendered callback to InfoWindow to rebind React Router, etc #202

Open blainegarrett opened 6 years ago

blainegarrett commented 6 years ago

Use case: The contents of my InfoWindow are Material-UI components that contain Buttons that utilize React Router and ultimately show a dialog. Currently, the InfoWindow component renders the children as a string and set this to the content of the infoWindow.setContent(renderedString). As such all event bindings are lost.

After experimenting, it seems infoWindow.setContent() can also take a dom element. If InfoWindow.renderChildren() could conditionally pass the rendered dom to a callback to further process it, we'd have an edge to rebind clickhandlers, etc.

I have gotten this to work on an older fork of google-maps-react. I have only tested it with React Router 3 and component. I assume react's sythentic events need to rebound for more general use, but I didn't need this for my use case. I'll try to branch latest master and make a PR adding this functionality.

lepirlouit commented 6 years ago

which is the status of this ?

alejo4373 commented 5 years ago

What can I do to help? This is affecting me as I need to put a <Link/> inside the InfoWindow or at least a onClick function to redirect users once they click on it. Also I ended up here by investigating the error message You should not use <Link> outside a <Router> given when I put a <Link/> inside the InfoWindow. I haven't figured a workaround yet.

blainegarrett commented 5 years ago

Unfortunately, I never had time to make a PR and I've since moved on from the project and I don't know that my solution is the best. Somewhere I started investigating the use of "Portals" but had to move on to other things.

That said, I ended up with something like:

// MyMapComponnent
  showPopup() {
    // This is the clickhandler bound in the renderDomCallback for the infoWindow
    // TODO: Investigate Portal?
    let slug = this.state.selectedPlace.slug;
    this.props.router.push({
      pathname: `/galleries/${slug}`,
      state: {
        modal: true,
        returnTo: '/gallery/guide?'
      }
    });
  }
  renderDomCallback(domElement) {
    // This is passed down to the InfoWindow
    // TODO: Investigate Portal
    let a = domElement.querySelector("a#galleryPopupLink"); // id of Material-UI <Button /> inside of VenueRenderer component
    if (a) {
      a.addEventListener('click', this.showPopup.bind(this));
    }
    return domElement;
  }

  render () { 
  ...

  return (
    <Map ...>
    ...
    <InfoWindow
          marker={this.state.activeMarker}
          visible={this.state.showingInfoWindow}
          ...
          renderDomCallback={this.renderDomCallback.bind(this)}>
            <MapInfoWindowContent>
              <VenueRenderer resource={ this.state.selectedPlace } />
            </MapInfoWindowContent>
        </InfoWindow>
    </Map>
   );

Wrap MyMapComponent in withRouter() HOC from react-router. I'm leveraging several Material-UI bits, but mine looks like: export default compose(withWidth(), withStyles(styles, { withTheme: true }))(withRouter(APIEnabledMap)); where ApiEnabledMap is

let APIMap = GoogleApiWrapper({
  apiKey: GOOGLE_MAPS_API_KEY,
  libraries: ['places','visualization'],
})(MyMapComponent);```

The change to google-maps-react Inside my forked google-maps-react/components/InfoWindow component class:

renderChildren() {
    const {children} = this.props;

    var domWrapper = document.createElement('DIV');
    domWrapper.innerHTML = ReactDOMServer.renderToString(children);

    if (this.props.renderDomCallback) {
      domWrapper = this.props.renderDomCallback(domWrapper);
    }

    return domWrapper;
  }

Here is that same method in current master sans callback: https://github.com/fullstackreact/google-maps-react/blob/master/src/components/InfoWindow.js#L96

Basically, once the dom is rendered inside the infowindow, I'm calling the provided callback I passed in via props. From here, I just find the target element and bind click handlers. Not ideal, but works.

As far as Material-UI goes, the component just acts as a ship to wrap the new dom inside of the MUI theme provider for styling. If you do not need it, you can remove it. Otherwise it looks like this:

export default function MapInfoWindowContent({children}) {
return (<ThemeProvider>{children}</ThemeProvider>);
}

That said, there might be a way to address this problem using "Portals" but I never dug that far into them. Either/or, I think you still would need a callback to wrap the dom.

More information on portals: https://reactjs.org/docs/portals.html

miliu99 commented 5 years ago

Any chance this problem gets resolved? I really need to use Link in InfoWindow. Thanks!

eur2 commented 5 years ago

Hey @blainegarrett, any chance to see the code above on a repo? I'm struggling to make the InfoWindow popup/modal available with a Route. It seems you have succeed? Many thanks in advance

titivermeesch commented 5 years ago

Is there still an activity here? I really need this to work but I can't figure it out...