React-leaflet: Dynamic layer ordering is not respected without key changes

Created on 10 Jan 2017  路  8Comments  路  Source: PaulLeCam/react-leaflet

Please make sure to check the following boxes before submitting an issue. Thanks!

  • [x] Check that all peer dependencies are installed: React, ReactDOM and Leaflet.
  • [x] Check that you are using a supported version of React and ReactDOM (v15.0.0+).
  • [x] Check that you are using the supported version of Leaflet (v.1.0.0) and its corresponding CSS file is loaded.
  • [x] Make sure you have followed the quick start guide for Leaflet.
  • [x] Make sure you have fully read the documentation and that you understand the technical considerations.

Expected behavior

Dynamically adding Map layers should respect layer ordering.

Actual behavior

Adding map layers without changing the layer keys can result in the layers being added in the wrong order.

Steps to reproduce

I have included a fiddle demonstrating the problem. In this fiddle I am using a simple index to keep track of the order in which layers were added. When I use that index as a key, the order in which I add layers does not matter. They will always be drawn with the most recent layer on top. If I use Math.random() as the layer key (forcing a re-render of each element, which is not performant) then the order in which I add layers will be respected.

Hopefully this .gif demonstrates the issue well enough to understand.
react-leaflet-layer-order

http://jsfiddle.net/jqfrwq3r/6/

Most helpful comment

@PaulLeCam It doesn't look like z-index is an option for GeoJSON layers.
My solution was to implement a custom ordering system based on a "priority" prop.

import 'leaflet-custom';
import { Map } from 'react-leaflet';
import L from 'leaflet';

export default class CustomBaseMap extends Map {

  createLeafletElement (props: Object): Object {
    return L.customMap(this.container, props);
  }

  componentDidUpdate (prevProps: Object) {
    /*
     * Leaflet GeoJSON layers have no concept of z-index. To achieve a similar
     * effect we use a "priority" prop to determine the order in which
     * "bringToFront()" calls are made. This should behave very similarly to z-index.
     */
    this.updateLeafletElement(prevProps, this.props);
    const layers = this.leafletElement._layers;
    Object.values(layers)
      .filter((layer) => {
        return typeof layer.options.priority !== "undefined";
      })
      .sort((layerA, layerB) => {
        return layerA.options.priority - layerB.options.priority;
      })
      .forEach((layer) => {
        layer.bringToFront();
      });
  }
}

All 8 comments

This is the expected behavior, as the rendering is done by Leaflet, not React. It's up to the applications to implement the logic they need on top of it.

Hmmm. Are you saying that it's expected that the key prop would determine the ordering (or lack thereof) of layers? I mean... I guess I could hack together a function that does repeated .bringToFront() calls but I can't imagine that's particularly efficient. Do you have a suggestion as to where I should deal with this? Perhaps by parsing and sorting the "children" prop of my custom map component?

The components tree changes are handled by React and the rendering (and therefore ordering) of layers is done by Leaflet, so if some layers change while others don't, React will only add and remove the relevant ones.
If your app needs a specific ordering, then using the key props can be an option to enforce components being added or removed even when their other props don't change, but if it's just a display concern, a better solution is likely to render the layers using a different z-index as necessary.

@PaulLeCam Gotcha. I'll try z-index. Thanks for the suggestion.

@PaulLeCam It doesn't look like z-index is an option for GeoJSON layers.
My solution was to implement a custom ordering system based on a "priority" prop.

import 'leaflet-custom';
import { Map } from 'react-leaflet';
import L from 'leaflet';

export default class CustomBaseMap extends Map {

  createLeafletElement (props: Object): Object {
    return L.customMap(this.container, props);
  }

  componentDidUpdate (prevProps: Object) {
    /*
     * Leaflet GeoJSON layers have no concept of z-index. To achieve a similar
     * effect we use a "priority" prop to determine the order in which
     * "bringToFront()" calls are made. This should behave very similarly to z-index.
     */
    this.updateLeafletElement(prevProps, this.props);
    const layers = this.leafletElement._layers;
    Object.values(layers)
      .filter((layer) => {
        return typeof layer.options.priority !== "undefined";
      })
      .sort((layerA, layerB) => {
        return layerA.options.priority - layerB.options.priority;
      })
      .forEach((layer) => {
        layer.bringToFront();
      });
  }
}

Yes looks like a good solution.

Also raised this in https://github.com/PaulLeCam/react-leaflet/issues/205 earlier. IMO the ordering of the layers should be an internal job for a component that wraps Leaflet's API so that the result is stateless and intuitive. Thanks for sharing your workaround @PaulLeCam, I'll use it in my next project with leaflet!

Hi,
There is a idiomatic way to archive this.
Namely using the Pane component and using css classes to set the z-index.

import { 
  Map,
  GeoJSON, 
  Pane,
} from 'react-leaflet';

..................

<Map>
  <Pane
      className="GridcellsPane"
    >
    <GeoJSON
      data={gridcells}
    />

  </Pane>
  <Pane
    className="GeoJSONPane"
  >
    <GeoJSON
      data={assets}
    />
  </Pane>
</Map>

..................

<style>
.GridcellsPane {
  z-index: 550;
}
.GeoJSONPane {
  z-index: 580;
}
</style>
Was this page helpful?
0 / 5 - 0 ratings