Skip to content
This repository has been archived by the owner on Aug 19, 2022. It is now read-only.

Disable component for SSR #35

Open
ptondereau opened this issue Jul 27, 2017 · 5 comments
Open

Disable component for SSR #35

ptondereau opened this issue Jul 27, 2017 · 5 comments

Comments

@ptondereau
Copy link

ptondereau commented Jul 27, 2017

I'm in situation where I need to render to null this component when window global is not present (because of SSR). I use this lib to avoid error in SSR node server --> https://github.com/masotime/react-leaflet-universal

I've forked your repo to change LayerGroup import origin and added same logic of render present in react-leaflet-universal but I don't really understand the API. Indeed, an error is throw about this.container.map is undefined so I can't access to the container.

Any clue for how to achieve this?

Here is my snippet:

import React, {Children, cloneElement} from 'react';
import PropTypes from 'prop-types';
import {LayerGroup} from 'react-leaflet-universal';

let L;

export default class MarkerClusterGroup extends LayerGroup {

  constructor() {
    super();
    this.state = { loaded: false };
  }

  componentDidMount() {
	  L = require('leaflet');
	  require('leaflet.markercluster');
	  this.setState({ loaded: true }, () => {
		  // Override auto created leafletElement with L.markerClusterGroup element
		  this.leafletElement = L.markerClusterGroup(this.props.options);

		  console.log(this.context);

		  if (this.props.markers.length) {
			  this.addLayersWithMarkersFromProps(this.props.markers);
		  }

		  this.props.wrapperOptions.enableDefaultStyle && (
			  this.context.map._container.className += ' marker-cluster-styled'
		  );

		  !this.props.wrapperOptions.disableDefaultAnimation && (
			  this.context.map._container.className += ' marker-cluster-animated'
		  );

		  // Init listeners for markerClusterGroup leafletElement only once
		  this.initEventListeners(this.leafletElement);
    });
  }

  componentWillReceiveProps(nextProps) {
    if (nextProps.markers.length && !isArraysEqual(this.props.markers, nextProps.markers)) {
      // Remove layer from map with previously rendered clustered markers
      this.layerContainer.removeLayer(this.leafletElement);
      // Remove layers with markers from markerClusterGroup
      this.leafletElement.clearLayers();

      this.addLayersWithMarkersFromProps(nextProps.markers);
    }
  }

  removeMarkersWithSameCoordinates(markers) {
    // init filtered markers list with first marker from list
    let filteredMarkers = [markers[0]];

    markers.forEach((marker) => {
      if (!JSON.stringify(filteredMarkers).includes(JSON.stringify(marker))) {
        filteredMarkers.push(marker);
      }
    });

    return filteredMarkers;
  }

  addLayersWithMarkersFromProps(markers) {
    let markersOptions = this.props.markerOptions
      ? Object.assign({}, this.props.markerOptions)
      : {};

    let filteredMarkers = this.props.wrapperOptions.removeDuplicates
      ? this.removeMarkersWithSameCoordinates(markers)
      : markers;

    let leafletMarkers = [];

    filteredMarkers.forEach((marker) => {
      let currentMarkerOptions = marker.options
        ? Object.assign({}, marker.options)
        : null ;

      let leafletMarker = L.marker(
        [marker.lat, marker.lng],
        currentMarkerOptions || markersOptions
      );

      marker.popup && leafletMarker.bindPopup(marker.popup);
      marker.tooltip && leafletMarker.bindTooltip(marker.tooltip);

      leafletMarkers.push(leafletMarker);
    });

    // Add markers leafletElements to the markerClusterGroup
    this.leafletElement.addLayers(leafletMarkers);
    // Add clustered markers to the leaflet map
    !this.props.children && this.layerContainer.addLayer(this.leafletElement);
  }

  initEventListeners(markerClusterGroup) {
    this.props.onMarkerClick && (
      markerClusterGroup.on('click', (marker) => {
        this.props.onMarkerClick(marker.layer);
      })
    );

    this.props.onClusterClick && (
      markerClusterGroup.on('clusterclick', (cluster) => {
        this.props.onClusterClick(cluster.layer);
      })
    );

    this.props.onPopupClose && (
      markerClusterGroup.on('popupclose', (map) => {
        this.props.onPopupClose(map.popup);
      })
    );
  }

  addLayersWithReactLeafletMarkers() {
    const leafletMarkers = [];

    // Map through all react-leaflet Markers and clone them with ref prop
    // ref prop required to get leafletElement of Marker
    return Children.map(this.props.children, (reactLeafletMarker, index) => (
      cloneElement(reactLeafletMarker, {
        ref: (marker) => {
          if (marker) {
            leafletMarkers.push(marker.leafletElement);

            if (
              (index === (this.props.children.length - 1)) ||
              // addClusteredMarkersToMap when there is only one marker
              !Array.isArray(this.props.children)
            ) {
              // Add markers leafletElements to the markerClusterGroup
              this.leafletElement.addLayers(leafletMarkers);
              // Add clustered markers to the leaflet map
              this.layerContainer.addLayer(this.leafletElement);
            }
          }
        },
        key: `react-leaflet-marker-${index}`
      })
    ));
  }

  getLeafletElement() {
    return this.leafletElement;
  }

  render() {
	  if (!this.state.loaded) {
		  return null;
	  }

    return this.props.children
    ? (
      <section className="marker-cluster-group">
        {this.addLayersWithReactLeafletMarkers()}
      </section>
    )
    : null;
  }
}

function isArraysEqual(firstArray, secondArray) {
  return (JSON.stringify(firstArray) === JSON.stringify(secondArray));
}

MarkerClusterGroup.propTypes = {
  // List of markers with required lat and lng keys
  markers: PropTypes.arrayOf(PropTypes.object),
  // List of react-leaflet markers
  children: PropTypes.node,
  // All available options for Leaflet.markercluster
  options: PropTypes.object,
  // All available options for Leaflet.Marker
  markerOptions: PropTypes.object,
  // Options that are supporting by react-leaflet-markercluster wrapper
  wrapperOptions: PropTypes.object,
  // Events
  onMarkerClick: PropTypes.func,
  onClusterClick: PropTypes.func,
  onPopupClose: PropTypes.func
};

MarkerClusterGroup.defaultProps = {
  markers: [],
  wrapperOptions: {}
};
@yuzhva
Copy link
Owner

yuzhva commented Jul 27, 2017

Looks like LayerGroup from react-leaflet-universal works not as expecting and MarkerClusterGroup component that extends it doesn't receive leaflet map object through props.

To use .addLayer(this.leafletElement); you need to get leafletElement (object crerated by leaflet lib) of the map, that is why I'm using:

export default class MarkerClusterGroup extends LayerGroup {

By extending extends LayerGroup react-leaflet gives me access to the leafletElement of the map...
I didn't investigate another way of getting it.

P.S: maybe try to ask react-leaflet-universal how they are getting leaflet object of the map inside custom classes?

@ptondereau
Copy link
Author

@wmertens
Copy link

wmertens commented Mar 25, 2019

another approach: theKashey/react-imported-component#47 (comment)

@owlyowl
Copy link

owlyowl commented Aug 29, 2019

@wmertens Do you have an example of leaflet working with react-imported-component?
I can't seem to get it working.

react-leaflet-universal isn't working with the google maps library

@wmertens
Copy link

@owlyowl I am not SSRing in this project, so no. I just pointed out the possibility. I'm also not using Google with leaflet. It should just work.

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

No branches or pull requests

4 participants