alex3165 / react-mapbox-gl

A React binding of mapbox-gl-js
http://alex3165.github.io/react-mapbox-gl/
MIT License
1.9k stars 534 forks source link

Prevent layer re-rendering when showing popup #710

Open cuttlas opened 5 years ago

cuttlas commented 5 years ago

I have a Layer of Markers that come from a tiles server:

        <Layer
          type="symbol"
          id="marker"
          sourceId="mn_staging_places"
          sourceLayer={"polygons"}
          images={["marker", image]}
          onMouseEnter={e => {
            const info = e.features[0].properties;
            const place = places.find(place => info.id === place.id);
            setInfoPopup(place);
          }}
          layout={{
            "icon-image": "marker",
            "icon-allow-overlap": true,
            "icon-ignore-placement": true
          }}
        >

When hovering one of the markers, I want to show a popup:

        {infoPopup  && (
          <Popup coordinates={[infoPopup.longitude, infoPopup.latitude]}>
            {renderInfoPopup()}
          </Popup>
        )}

It works, but the layer markers hide for one second just after hovering. My guess is that showing/hidding the popup causes the Map to rerender, so the layer is rendered again, causing the flickering effect. Is there a way to prevent this behaviour??

Thanks.

berpcor commented 4 years ago

The same problem. Anybody?

agusterodin commented 4 years ago

Is your source being set using a spread operator or are you using any spread operators in your mapbox react code in general? If so, that is what will cause the flicker because this wrapper will think something drastic changed whenever it sees that a new object was passed to it. See if that fixes the issue.

AdriSolid commented 4 years ago

@cuttlas a quick fix could be to create a component with 'Layer' and 'Popup' childs (above 'Layers' component, waiting until your source is loaded), and in same level, adding the 'Source' component. An example of a possible solution could be:

· Main component

import React from 'react';
import ReactMapboxGL, { MapContext, Source, ZoomControl } from 'react-mapbox-gl';
import Layers from '../../layers/Layers';

const Map = ReactMapboxGL({
  accessToken:
    'pk.eyJ1IjoiZG90Z2lzIiwiYSI6ImNqd3Z6amtjMTBjOTA0OW84ZjVvYzF6bjQifQ.LIbUaYq3GaiWTzsBV6YnTA'
});

const style = 'mapbox://styles/mapbox/dark-v10';

const LAYERS_CONFIG = {
  cadastre_id: {
    id: 'public.test',
    sourceId: 'cadastre_source_id',
    sourceLayer: 'public.test'
  }
};

const Main = () => {
  const [sourcesLoaded, setLoaded] = React.useState({
    testLayer: false
  });

  const [allLoaded, setAllLoaded] = React.useState(null);

  React.useEffect(() => {
    if (sourcesLoaded) {
      const all = Object.keys(sourcesLoaded).every(k => sourcesLoaded[k] === true);
      all && setAllLoaded(true);
    }
  }, [sourcesLoaded]);

  const manageLayersLoaded = layer => {
    setLoaded({ ...sourcesLoaded, [layer]: true });
  };

  return (
    <>
      <Map
        style={(styleHack => styleHack)(style)}
        center={[-3.70379, 40.416775]}
        zoom={[5]}
        dragRotate={false}
        movingMethod="jumpTo"
        containerStyle={{
          position: 'absolute',
          height: '100%',
          width: '100%',
          top: 0,
          padding: 0,
          margin: 0
        }}
      >
        <MapContext.Consumer>
          {map => (
              <>
                <ZoomControl key="zoom-control" position="top-left" />
                <Source
                  id={LAYERS_CONFIG.cadastre_id.sourceId}
                  tileJsonSource={{
                    type: 'vector',
                    tiles: ['http://devsolargalp.dotgiscorp.com:3000/public.test/{z}/{x}/{y}.pbf'],
                    promoteId: 'postal_code'
                  }}
                  onSourceLoaded={() => manageLayersLoaded('testLayer')}
                />
                {allLoaded && <Layers key="map" map={map} config={LAYERS_CONFIG} />}
              </>
            )
          }
        </MapContext.Consumer>
      </Map>
    </>
  );
};

export default Main;

· Layers component (with Popup)

import React from 'react';
import PropTypes from 'prop-types';
import { Map } from 'mapbox-gl';
import { Layer, Popup } from 'react-mapbox-gl';
import useMapEvents from './useMapEvents';
import PAINT from './paints';
import PopupTemplate from '../components/UI/PopupTemplate';

const Layers = ({ map, config }) => {
  const [popupConfig] = useMapEvents(map, config);

  return (
    <>
      <Layer
        type="fill"
        id={config.cadastre_id.id}
        sourceId={config.cadastre_id.sourceId}
        sourceLayer={config.cadastre_id.sourceLayer}
        paint={PAINT.cadastrePaint}
      />
      {popupConfig && popupConfig.coords &&  (
        <Popup
          coordinates={popupConfig.coords}
          style={{ display: popupConfig.shouldRender ? '' : 'none' }}
        >
          <PopupTemplate label={popupConfig.staticLabel} content={popupConfig.info} />
        </Popup>
      )}
    </>
  );
};

Layers.propTypes = {
  map: PropTypes.instanceOf(Map).isRequired,
  config: PropTypes.object.isRequired
};

export default Layers;