vemund
vemund

Reputation: 1817

The correct way to update props with Mapbox

What is the correct way to remove and add a source/layers with Mapbox? I am using React, and have issues and get an error when a the source data prop is updated. As far as I have read in the documentation of Mapbox removeSource should remove it before adding it again, but it is not working on a component update.

Error: There is already a source with this ID

  componentDidMount() {
    const { data } = this.props;
    this.map = new mapboxgl.Map(config);
  }

  componentWillUnmount() {
    const map = this.map;
    map.remove();
    map.removeControl(Draw, 'top-left');
  }


  shouldComponentUpdate(props, nextProps) {
    const { data } = props;
    if (JSON.stringify(data) !== JSON.stringify(nextProps.data)) {
      return true;
    }
    return false;
  }


  componentDidUpdate(prevProps) {
    const { data } = this.props;
    if (JSON.stringify(data) !== JSON.stringify(prevProps.data)) {
      this.fetchMap();
    }
  }

  fetchMap() {
        const map = this.map;
        const { data } = this.props;
        map.addControl(Draw, 'top-left');

        map.on("load", (e) => {
            if (data.features !== null) {

                if (map.getSource("locations")) {
                    map.removeSource("locations");
                }

                map.addSource("locations", {
                    type: "geojson",
                    data: data
                });
            }
        })
  }

Upvotes: 4

Views: 3182

Answers (1)

Stepan Kuzmin
Stepan Kuzmin

Reputation: 1031

The problem is that you call map.addSource multiple times during your component lifecycle.

If I understood correctly, what are you trying to achieve, then you should move your map load event handler to the componentDidMount method:

componentDidMount() {
  const { data } = this.props;

  const map = new mapboxgl.Map(config);
  map.addControl(Draw, 'top-left');

  map.once("load", (e) => {
    map.addSource("locations", {
        type: "geojson",
        data: data
    });

    this.setState({ mapIsLoaded: true });
  });

  this.map = map;
}

Note the usage of map.once instead of map.on and setting mapIsLoaded state variable, after the map is done the loading.

Now you can handle your source update on componentDidUpdate after map is loaded:

componentDidUpdate(prevProps) {
  const { data } = this.props;
  const { mapIsLoaded } = this.state;

  if (!mapIsLoaded) {
    return;
  }

  if (data !== prevProps.data) {
    this.map.getSource("locations").setData(data);
  }
}

That's it.


By the way, I'm the author of React Component Library for Mapbox GL JS. The project is intended to be as close as possible to the Mapbox GL JS API.

For example, this is how you can rewrite your code with the library:

<MapGL mapStyle='mapbox://styles/mapbox/light-v9'>
  <Source id='locations' type='geojson' data={data} />
  <Layer
    id='locations'
    type='circle'
    source='locations'
    paint={{
      'circle-radius': 6,
      'circle-color': '#1978c8'
    }}
  />
  <Draw />
</MapGL>

Upvotes: 6

Related Questions