Skip to content
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

basemapLayer: Cannot set property 'innerHTML' of null in React app #945

Closed
forgo opened this issue Apr 10, 2017 · 10 comments
Closed

basemapLayer: Cannot set property 'innerHTML' of null in React app #945

forgo opened this issue Apr 10, 2017 · 10 comments

Comments

@forgo
Copy link

forgo commented Apr 10, 2017

Chrome 57.0.2987.133 (64-bit), Safari 10.1
Using NPM/Node/Webpack and React:

"esri-leaflet": "^2.0.8",
"leaflet": "^1.0.3",

The single line of code utilizing esri-leaflet is for sure the culprit, as removing it (and thus the baselayer of my maps) stops the error from popping up in the developer console in Chrome and Safari. Everything seems to be functioning alright, but my console is inundated with these errors, and I need to remove them to make this a production-ready application.

VM20550:819 Uncaught TypeError: Cannot set property 'innerHTML' of null
    at _updateMapAttribution (eval at <anonymous> (http://localhost:10099/bundle-993595c0a6d1bc0978ee.js:1:1381880), <anonymous>:819:35)
    at eval (eval at <anonymous> (http://localhost:10099/bundle-993595c0a6d1bc0978ee.js:1:1381880), <anonymous>:790:6)
    at Object.window._EsriLeafletCallbacks.(anonymous function) [as c3] (eval at <anonymous> (http://localhost:10099/bundle-993595c0a6d1bc0978ee.js:1:1381880), <anonymous>:202:17)
    at https://static.arcgis.com/attribution/World_Topo_Map?callback=window._EsriLeafletCallbacks.c3&f=json:1:30
_updateMapAttribution @ VM20550:819
(anonymous) @ VM20550:790
window._EsriLeafletCallbacks.(anonymous function) @ VM20550:202
(anonymous) @ World_Topo_Map?callback=window._EsriLeafletCallbacks.c3&f=json:1

Steps to reproduce the error:

  1. Create a map with leaflet in React component:

import L from 'leaflet'
import E from 'esri-leaflet'

Offending line:
// E.basemapLayer(esriBasemapLayer).addTo(this.map);
where: esriBasemapLayer = "Topographic";

I took a lot of time to create a vanilla Node/Webpack/React project to reproduce this error as simply as possible, but unfortunately, I haven't been able to reproduce the problem in a stripped down way.

So I am resorting to showing the majority of my React component:

import React from 'react'
import ReactDOM from 'react-dom'
import L from 'leaflet'
import E from 'esri-leaflet'

export class Map extends React.Component {
    render() {
        const { esriBasemapLayer, geoJSONData, clickEventDetail, clickPictureDetail } = this.props;
        return (
            <div className={Styles.Map}>
                <div ref={() => this.renderMap(esriBasemapLayer, geoJSONData, clickEventDetail, clickPictureDetail)} />
            </div>
        )
    }
    renderMap(esriBasemapLayer, geoJSONData, clickEventDetail, clickPictureDetail) {
        if (this.map) {
            this.map.remove();
        }
        let mapContainer = ReactDOM.findDOMNode(this);

        // configure default marker map icons
        delete L.Icon.Default.prototype._getIconUrl;
        L.Icon.Default.mergeOptions({
            iconRetinaUrl: require("../img/leaflet/marker-icon-2x.png"),
            iconUrl: require("../img/leaflet/marker-icon.png"),
            shadowUrl: require("../img/leaflet/marker-shadow.png")
        });

        let eventMarkerIcon = L.icon({
            iconRetinaUrl: require("../img/leaflet/marker-event.png"),
            iconUrl: require("../img/leaflet/marker-event.png"),
            // shadowUrl: require("../img/leaflet/marker-event-shadow.png"),
            iconSize: [30, 27],   // size of the icon
            shadowSize: [30, 27], // size of the shadow
            iconAnchor: [15, 14], // point of the icon which will correspond to the marker's location
            shadowAnchor: [15, 14], // the same for the shadow
            popupAnchor: [0, -27], // point from which the popup should open relative to the iconAnchor
        });

        let pictureMarkerIcon = L.icon({
            iconRetinaUrl: require("../img/leaflet/marker-picture.png"),
            iconUrl: require("../img/leaflet/marker-picture.png"),
            // shadowUrl: require("../img/leaflet/marker-event-shadow.png"),
            iconSize: [32, 32],   // size of the icon
            shadowSize: [32, 32], // size of the shadow
            iconAnchor: [16, 16], // point of the icon which will correspond to the marker's location
            shadowAnchor: [16, 16], // the same for the shadow
            popupAnchor: [0, -32], // point from which the popup should open relative to the iconAnchor
        });

        this.map = L.map(mapContainer);

        // configure map control settings
        this.map.scrollWheelZoom.disable();
        this.map.keyboard.disable();

        // THIS LINE CAUSES THE ERROR
        E.basemapLayer(esriBasemapLayer).addTo(this.map);
        
        const geoJSON = L.geoJSON(geoJSONData, {
            pointToLayer: (feature, latlng) => {
                console.log("feature:", feature);
                if(feature.properties.name === "Event") {
                    return L.marker(latlng, {icon: eventMarkerIcon});
                }
                else if(feature.properties.name === "Picture") {
                    return L.marker(latlng, {icon: pictureMarkerIcon});
                }
                else {
                    return L.Icon.Default;
                }
            },
            onEachFeature: (feature, layer) => {
                console.log("EventDetail::onEachFeature");
                const popupImgSrc = feature.properties.imgSrc;
                const popupImg = "<img class=\""+ Styles.popupImg +"\" src=\""+popupImgSrc+"\"/>";
                const popupDescription = "<div class=\""+ Styles.popupDescription +"\">"+feature.properties.description+"</div>";
                const popup = "<div class=\""+ Styles.popup +"\">"+ popupImg + popupDescription +"</div>";


                layer.bindPopup(popup);
                layer.on('mouseover', () => { layer.openPopup() });
                layer.on('mouseout', () => { layer.closePopup() });
                layer.on('click', () => {
                    if(feature.properties.name === "Event") {
                        clickEventDetail(feature.properties.id);
                    }
                    else if(feature.properties.name === "Picture") {
                        clickPictureDetail(feature.properties.id);
                    }
                });
            }
        });
        MapUtils.setBoundsAndZoom(this.map, geoJSON);
        geoJSON.addTo(this.map);
    }
}

export default Map
@jgravois
Copy link
Contributor

@forgo
Copy link
Author

forgo commented Apr 11, 2017

I've been able to strip down to a more simplified reproduction of the error. I have moved the definition of leaflet and esri-leaflet into componentDidMount for reasons mentioned here.

Otherwise, I have removed any other customizations surrounding my marker placement, etc...

I am simply setting the coordinate/zoom and adding the baselayer. This is enough to reproduce 5 of the same error messages in my Javascript console mentioned above:

VM37861:819 Uncaught TypeError: Cannot set property 'innerHTML' of null
    at _updateMapAttribution (eval at <anonymous> (http://localhost:10099/bundle-d63c4b074c1ba077863d.js:1:2952079), <anonymous>:819:35)
    at eval (eval at <anonymous> (http://localhost:10099/bundle-d63c4b074c1ba077863d.js:1:2952079), <anonymous>:790:6)
    at Object.window._EsriLeafletCallbacks.(anonymous function) [as c1] (eval at <anonymous> (http://localhost:10099/bundle-d63c4b074c1ba077863d.js:1:2952079), <anonymous>:202:17)
    at https://static.arcgis.com/attribution/World_Topo_Map?callback=window._EsriLeafletCallbacks.c1&f=json:1:30

Component:

import React from 'react'
import ReactDOM from 'react-dom'
import Styles from './Map.css'

let L, E;

export class Map extends React.Component {

    componentDidMount() {
        // only runs on client, not on server render
        L = require("leaflet");
        E = require("esri-leaflet");
        this.forceUpdate();
    }

    render() {
        return (
            <div className={Styles.Map}>
                <div className={Styles.mapContainer} ref={() => this.renderMap()} /> 
            </div>
        )
    }
    renderMap() {
        if(!L || !E) {
            return;
        }
        if (this.map) {
            this.map.remove();
        }
        let mapContainer = ReactDOM.findDOMNode(this);
        this.map = L.map(mapContainer);
        this.map.setView([45, 137], 6);
        E.basemapLayer("Topographic").addTo(this.map);
    }
}

export default Map

@jgravois
Copy link
Contributor

thanks for that. i'm juggling a couple other things at the moment, but i'll be able to dig in deeper in a couple days.

@jgravois
Copy link
Contributor

i still can't reproduce this error.

// @1.0.3 / @2.0.8
import L from 'leaflet'
import esri from 'esri-leaflet'

// @15.5.4
import React, { Component } from 'react'
import ReactDOM from 'react-dom'

class Map extends Component {
  constructor (props) {
    super(props)
  }

  render () {
    const style = { position: 'absolute', top:'0', bottom:'0', right:'0', left:'0' };

    return (
      <div style={style}>
          <div ref={() => this.renderMap()} />
      </div>
    )
  }

  renderMap() {
    let mapContainer = ReactDOM.findDOMNode(this);
    this.map = L.map(mapContainer);
    this.map.setView([45, 137], 6);
    esri.basemapLayer("Topographic").addTo(this.map);
  }
}

ReactDOM.render(<Map />, document.getElementById('react-app'))
<!DOCTYPE html>
<html>
  <head>
    <meta charset=utf-8>
    <title>github user map | react</title>
    <meta name='viewport' content='width=device-width,initial-scale=1,user-scalable=no'>
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/leaflet/1.0.3/leaflet.css">
    <style>
      html,
      body {
        margin: 0;
        padding: 0;
      }
    </style>
  </head>
  <body>
  <div id="react-app"></div>
  <script src="bundle.js"></script>
  </body>
</html>

putting that aside for a moment, in your app it appears we fail to introspect the DOM and set basemap attribution:

var attributionElement = map.attributionControl._container.querySelector('.esri-attributions');
attributionElement.innerHTML = newAttributions;

screenshot 2017-04-21 14 46 53

is that <span> actually drawing in your app?

@jgravois
Copy link
Contributor

jgravois commented May 1, 2017

you ever track down the cause of your error @forgo?

@forgo
Copy link
Author

forgo commented May 4, 2017

Sorry it's taken me a while to respond. I have been busy working on my team's continuous integration environment. Now I've kind of circled back to this problem, and I would like to get rid of these errors. I will start diving in and let you know what I find. Thanks for looking into it so far.

@forgo
Copy link
Author

forgo commented May 4, 2017

I get the <span> you referred to earlier, but the span class is class="esri-dynamic-attribution" and not the class="esri-attributions" I see in your screenshot.

I noticed in your Util.js that it is looking for "esri-dynamic-attribution", and not the "esri-attributions" you showed in your code snippets above.

The Util.js under esri-leaflet/src in my node_modules appears to be looking for the right "esri-dynamic-attribution" as well, so I'm not sure this is the root of the issue or not.

@forgo
Copy link
Author

forgo commented May 4, 2017

I have a suspicion it might have something to do with the react ref={()=>this.renderMap(..)}

I noticed that renderMap is getting called twice, back-to-back sometimes. Hard to say if this is expected, but it doesn't really make sense to me, considering I only have 0 or 1 maps on a page at a time.

If you have a better way of rendering a map in a React component without using the "ref" attribute, I would am open to getting rid of it, as I was simply borrowing that method via another project my team works on, and I didn't really understand completely how the "ref" works in React.

@forgo
Copy link
Author

forgo commented May 4, 2017

Looks like I was on the right track:

I removed the ref={()=>this.renderMap(..)} from within the my Map React component's render method entirely.

Instead I called my renderMap function in componentDidUpdate instead, extracting what I needed out of my props there. This prevented the render from occuring twice as mentioned above and also removed the errors I was seeing:

componentDidUpdate() {
    const { esriBasemapLayer, geoJSONData, clickEventDetail, clickPictureDetail, addBreadcrumbFromMap } = this.props;
    this.renderMap(esriBasemapLayer, geoJSONData, clickEventDetail, clickPictureDetail, addBreadcrumbFromMap)
}

Hope this is helpful to others who may run into similar issues.

Thanks again for looking into this!

@forgo forgo closed this as completed May 4, 2017
@jgravois
Copy link
Contributor

jgravois commented May 4, 2017

no worries at all. thanks for taking the time to close the loop @forgo.

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

2 participants