Skip to content

Non-Recompose examples #636

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Kalcode opened this issue Sep 27, 2017 · 19 comments
Closed

Non-Recompose examples #636

Kalcode opened this issue Sep 27, 2017 · 19 comments

Comments

@Kalcode
Copy link

Kalcode commented Sep 27, 2017

Seems this library was written to be used with just React. The documentation doesn't say it depends on recompose. I've got it working without recompose.

Is there a reason this react package is documented with recompose only?

Unwinding what recompose was doing so I could write it in just React(es6/7) was painful and I am sure other don't want to go through that too. Though i'd ask in case there was a something that I was missing.

Thanks.

@tomchentw
Copy link
Owner

tomchentw commented Sep 28, 2017

You can totally use vanilla React without recompose. The documentation is written with recompose just for simplicity.

Just notice three things when using it:

  1. You need to wrap your map elements render function inside a withGoogleMap HOC
  2. You need to wrap 1. into withScriptjs HOC, or you'll have to load Google Maps JavaScript API manually in your HTML <head> tag
  3. For the component from 2., you need to provide necessary props for it to work containerElement/loadingElement/mapElement/googleMapURL

From that, it should be:

const MyMapComponent = withScriptjs(withGoogleMap(props => {
  return <GoogleMap><Marker /></GoogleMap>
}))

<MyMapComponent
  googleMapURL="https://maps.googleapis.com/maps/api/js?v=3.exp&libraries=geometry,drawing,places"
  loadingElement={<div style={{ height: `100%` }} />}
  containerElement={<div style={{ height: `400px` }} />}
  mapElement={<div style={{ height: `100%` }} />}
/>

@sangel10
Copy link

sangel10 commented Sep 29, 2017

Could you show an an example using class X extends React.Component syntax?

@tomchentw
Copy link
Owner

@sangel10 you just use the MyMapComponent in your class X extends React.Component's render function. It's totally up to you to implement state changes in any way (redux, react local state, recompose……etc)

@sangel10
Copy link

That works for simple examples but what about an example using nested components?
e.g. https://tomchentw.github.io/react-google-maps/#markerclusterer

@tomchentw
Copy link
Owner

@sangel10 I updated the example for you. Is it clear enough now?
https://tomchentw.github.io/react-google-maps/#!/MarkerClusterer

@MonsieurV
Copy link

MonsieurV commented Oct 3, 2017

You can also create a wrapper of the form:

GoogleMapsWrapper.js

import React from 'react';
import { GoogleMap,withGoogleMap,withScriptjs } from 'react-google-maps';

export default const GoogleMapsWrapper = withScriptjs(withGoogleMap(props => {
  return <GoogleMap {...props} ref={props.onMapMounted}>{props.children}</GoogleMap>
}));

Then use it as GoogleMap in the example, without any recompose stuff. You can pass props and nest components exactly like for GoogleMap:

main.js

import React from 'react';
import GoogleMapsWrapper from './GoogleMapsWrapper.js';
import { Marker } from 'react-google-maps';
import MarkerClusterer from "react-google-maps/lib/components/addons/MarkerClusterer";

class DemoApp extends React.Component {
	componentWillMount() {
		this.setState({ markers: [] })
	}
	
	componentDidMount() {
		const url = [
			// Length issue
			`https://gist.githubusercontent.com`,
			`/farrrr/dfda7dd7fccfec5474d3`,
			`/raw/758852bbc1979f6c4522ab4e92d1c92cba8fb0dc/data.json`
		].join("")
		
		fetch(url)
		.then(res => res.json())
		.then(data => {
			this.setState({ markers: data.photos });
		});
	}
	
	render () {
		return (
			<GoogleMapsWrapper
				googleMapURL="https://maps.googleapis.com/maps/api/js?v=3.exp&libraries=geometry,drawing,places"
				loadingElement={<div style={{ height: `100%` }} />}
				containerElement={<div style={{ height: `400px` }} />}
				mapElement={<div style={{ height: `100%` }}
				defaultZoom={3}
				defaultCenter={{ lat: 25.0391667, lng: 121.525 }}>
				<MarkerClusterer
					averageCenter
					enableRetinaIcons
					gridSize={60}>
					{this.state.markers.map(marker => (
						<Marker
							key={marker.photo_id}
							position={{ lat: marker.latitude, lng: marker.longitude }}
							/>
					))}
				</MarkerClusterer>
			</GoogleMapsWrapper>
		);
	}
}

@JarLowrey
Copy link

JarLowrey commented Oct 3, 2017

@MonsieurV your mapElement prop is missing a />}. And the GoogleMapsWrapper throws an error. Apparently the wrapper/export should look like this:

const GoogleMapsWrapper = withScriptjs(withGoogleMap ...
export default GoogleMapsWrapper;

After these changes @MonsieurV's code works for me. Does this look good to everyone? Would be very helpful to see something like this in the docs.

@JarLowrey
Copy link

JarLowrey commented Oct 4, 2017

Ran into an issue with that ES6 React code. When I attempted to add an onBoundsChanged callback the getBounds and getCenter functions threw an error. Eventually found that I was passing in the onMapMounted prop incorrectly, decided to post here for posterity.

GoogleMapsWrapper.js:

import React from 'react';
import { GoogleMap,withGoogleMap,withScriptjs } from 'react-google-maps';

const GoogleMapsWrapper = withScriptjs(withGoogleMap(props => {
  return <GoogleMap {...props} ref={props.onMapMounted}>{props.children}</GoogleMap>
}));

export default GoogleMapsWrapper;

map.jsx

import GoogleMapsWrapper from './GoogleMapsWrapper.js';
import { Marker } from 'react-google-maps';
import MarkerClusterer from "react-google-maps/lib/components/addons/MarkerClusterer";

export default class MapSearch extends React.Component {
    componentWillMount() {
        let refs = {};

        this.setState({
            markers: [],
            onMapMounted: map => {
                refs.map = map;
            },
            onBoundsChanged: () => {
                console.log(refs.map) // (not a Container, a Map) Map {props: {…}, context: {…}, refs: {…}, updater: {…}, _reactInternalFiber: FiberNode, …}                
                this.setState({
                    bounds: refs.map.getBounds(),
                    center: refs.map.getCenter()
                });
            }
        });
    }

    render() {
        return (            
         <GoogleMapsWrapper
                googleMapURL="https://maps.googleapis.com/maps/api/js?key=AIzaSyCMh8-5D3mJSXspmJrhSTtt0ToGiA-JLBc&libraries=geometry,drawing,places" // libraries=geometry,drawing,places
                loadingElement={<div style={{ height: `100%` }} />}
                containerElement={<div style={{ height: `400px` }} />}
                mapElement={<div style={{ height: `100%` }} />}
                defaultZoom={12}
                defaultCenter={{ lat: 37.7749, lng: -122.4194 }}
                onMapMounted={this.state.onMapMounted}
                onBoundsChanged={this.state.onBoundsChanged}
            >
                <MarkerClusterer
                    averageCenter
                    enableRetinaIcons
                    gridSize={60}
                >
                    {this.state.markers.map(marker => (
                        <Marker
                            key={marker.photo_id}
                            position={{ lat: marker.latitude, lng: marker.longitude }}
                        />
                    ))}
                </MarkerClusterer>
            </GoogleMapsWrapper>
        );
    }
}

@tomchentw
Copy link
Owner

@JarLowrey yeah, because ref callback needs to be assigned to the <GoogleMap> element, not the <GoogleMapsWrapper> one.

@VinceBT
Copy link

VinceBT commented Jan 23, 2018

For anyone reading this in the future, please NEVER put functions in the state like @JarLowrey did, this is not a good practice. The state is for keeping internal data relative to the rendering of your component and nothing else

PS: As shown in the code, the ref can be null at the componentDidMount phase but it's because that the <GoogleMapsWrapper /> loses the hand over the lifecycle phase of the <GoogleMap />, but will be defined a moment after the mount. I've never seen a case like this, it may happen because of withScriptjs & withGoogleMap.

Use this safe way to set the ref (with the help of @MonsieurV's wrapper) edited multiple times to include a working PoC:

import React from 'react';
import {GoogleMap, Marker, withGoogleMap, withScriptjs} from 'react-google-maps';
import MarkerClusterer from "react-google-maps/lib/components/addons/MarkerClusterer";

const GoogleMapsWrapper = withScriptjs(withGoogleMap(props => {
  const {onMapMounted, ...otherProps} = props;
  return <GoogleMap {...otherProps} ref={c => {
    onMapMounted && onMapMounted(c)
  }}>{props.children}</GoogleMap>
}));

export default class MapPage extends React.Component {

  state = {
    markers: [],
  };

  componentDidMount() {
    console.log('Mounted @ ' + Date.now());
    const url = "https://gist.githubusercontent.com/farrrr/dfda7dd7fccfec5474d3/raw/758852bbc1979f6c4522ab4e92d1c92cba8fb0dc/data.json";
    fetch(url)
      .then(res => res.json())
      .then(data => {
        this.setState({markers: data.photos});
      });
  }

  _mapRef = null;

  _handleMapMounted = (c) => {
    if (!c || this._mapRef) return;
    this._mapRef = c;
    console.log('Ref set later @ ' + Date.now());
  };

  _handleBoundsChanged = () => {
    if (!this._mapRef) return;
    const center = this._mapRef.getCenter();
    const bounds = this._mapRef.getBounds();
    // console.log(center, bounds);
  };

  render() {
    return (
      <GoogleMapsWrapper
        googleMapURL="https://maps.googleapis.com/maps/api/js?v=3.exp&libraries=geometry,drawing,places"
        loadingElement={<div style={{height: `100%`}}/>}
        containerElement={<div style={{height: `100%`}}/>}
        mapElement={<div style={{height: `100%`}}/>}
        defaultZoom={3}
        defaultCenter={{lat: 25.0391667, lng: 121.525}}
        onMapMounted={this._handleMapMounted}
        onBoundsChanged={this._handleBoundsChanged}>
        <MarkerClusterer
          averageCenter
          enableRetinaIcons
          gridSize={60}>
          {this.state.markers.map(marker => (
            <Marker
              key={marker.photo_id}
              position={{lat: marker.latitude, lng: marker.longitude}}
            />
          ))}
        </MarkerClusterer>
      </GoogleMapsWrapper>
    )
  }
}

PS2: Other bad practices spotted:

  • Never call this.setState in componentWillMount
  • In componentWillMount, refs are not defined yet you have to wait until in componentDidMount
  • A setState to put a function in the state that sets the state ?

@JarLowrey
Copy link

@VinceBT I copy/pasted from the code in the documentation. If this is poor design, which I can understand, perhaps the react-google-maps docs should be updated?

@VinceBT
Copy link

VinceBT commented Jan 25, 2018

Always take some steps back with React since it is a growing framework, there are some ideologies that have grown around it, some are good, some are bad, it's your job to not spread bad practices. Every time I see your code I notice a new one and I update my post to include it.

Could you indicate the precises blocks with the bad practices I mentionned that you copied ? If there are, it would be a great idea to submit a PR then.

@JarLowrey
Copy link

Map with a SearchBox, Standalone SearchBox has a function in the state that sets state.

Map with a MarkerClusterer calls setState in componentWillMount.

Standalone SearchBox and Map with a SearchBox use refs in componentWillMount.

@VinceBT
Copy link

VinceBT commented Jan 25, 2018

Well that's a pretty bad doc then.
I wouldn't believe any piece of code/documentation that's written by the same guy that makes a library that need recompose for obscure reasons, my two cents. Take the good practices from the React documentation and nowhere else.

@joelmenezes
Copy link

@VinceBT I used your PoC code and just copy pasted it onto a new file and imported the MapPage component in my index.js. I see the markers being imported and the console logs for the refs, but the app screen is blank. Is there something I am missing?

My React and react-dom versions are 16.4.0 each.

@hungdt-ibl
Copy link

where can i get "google" ?

  const DirectionsService = new google.maps.DirectionsService()

My component:

class Map extends Component {
  constructor (props) {
    super(props)
    this.state = {
      searchText: ''
    }
  }
  componentDidMount () {
    // google ?
    const DirectionsService = new google.maps.DirectionsService()
  }
  render () {
    const { location, directions } = this.props
    if (!location) {
      return null
    }
    return (
      <GoogleMap
        defaultZoom={18}
        defaultCenter={location}
        defaultOptions={MAP_OPTIONS}
        center={location}
      >
        {directions && <DirectionsRenderer directions={directions} />}
        <Marker
          position={location}
        />
      </GoogleMap>
    )
  }
}

export default withScriptjs(withGoogleMap(Map))

@ViktorZhurbin
Copy link

@hungdt-ibl
Grab it from window (window.google)
Hope this helps

@louisdvs
Copy link

louisdvs commented Jul 3, 2018

@VinceBT thanks for the code snippet above, huge help! Was struggling with this for a while.

@baughmann
Copy link

For posterity, if anyone wants a more fleshed-out example...

import React, { Component } from "react";
// needing for panning and zooming the map to a specific point
import { MAP } from "react-google-maps/lib/constants";
import {
  withScriptjs,
  withGoogleMap,
  GoogleMap,
  Marker
} from "react-google-maps";

const Map = withScriptjs(
  withGoogleMap(props => (
    <GoogleMap
      ref={props.onMapMounted}
      defaultZoom={8}
      defaultCenter={{ lat: 38, lng: -77 }}
    >
      {/* display markers, polylines, whatever inside the map */}
      {props.children}
    </GoogleMap>
  ))
);

export default class MyMap extends Component {
  constructor(props) {
    super(props);

    this.zoomToPoint = this.zoomToPoint.bind(this);
    this.onMapMounted = this.onMapMounted.bind(this);

    this.map = React.createRef();
    this.mapObject = React.createRef();
  }

  onMapMounted(ref) {
    this.map = ref;
    // needing for panning and zooming the map to a specific point
    this.mapObject = ref ? ref.context[MAP] : null;
  }

  // used to pan to a google.maps.LatLng
  zoomToPoint(position) {
    if (this.map && this.mapObject) {
      this.map.panTo(position);
      this.mapObject.setZoom(17);
    }
  }

  render() {
    return (
            <Map
              onMapMounted={this.onMapMounted}
              googleMapURL="<YOUR GOOGLE MAPS URL>"
              loadingElement={<div style={{ height: `100%`, width: `100%` }} />}
              containerElement={
                <div
                  id="map-container"
                  style={{ height: `100%`, width: `100%` }}
                />
              }
              mapElement={
                <div id="map" style={{ height: `100%`, width: `100%` }} />
              }
            >
              {/* render markers or polylines etc in here like you would inside GoogleMap */}
              {markers.map(m => (
                        <Marker
                          key={m.id}
                          position={{
                            lat: m.latitude,
                            lng: m.longitude
                          }}
                        />
              )}
            </Map>
    );
  }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests