alex3165 / react-mapbox-gl

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

"Cannot read property 'on' of undefined" in layer-events-hoc.js #663

Open jvskriubakken opened 5 years ago

jvskriubakken commented 5 years ago

Please help!

We where planning to release at the end of this week and suddenly our application using react-mapbox-gl fail s Stacktrace:

layer-events-hoc.js:263 Uncaught TypeError: Cannot read property 'on' of undefined
    at EnhancedLayer.componentWillMount (layer-events-hoc.js:263)
    at callComponentWillMount (react-dom.development.js:13025)
    at mountClassInstance (react-dom.development.js:13123)
    at updateClassComponent (react-dom.development.js:14854)
    at beginWork (react-dom.development.js:15716)
    at performUnitOfWork (react-dom.development.js:18750)
    at workLoop (react-dom.development.js:18791)
    at HTMLUnknownElement.callCallback (react-dom.development.js:147)
    at Object.invokeGuardedCallbackDev (react-dom.development.js:196)
    at invokeGuardedCallback (react-dom.development.js:250)
    at replayUnitOfWork (react-dom.development.js:17998)
    at renderRoot (react-dom.development.js:18909)
    at performWorkOnRoot (react-dom.development.js:19812)
    at performWork (react-dom.development.js:19722)
    at performSyncWork (react-dom.development.js:19696)
    at requestWork (react-dom.development.js:19551)
    at scheduleWork (react-dom.development.js:19358)
    at Object.enqueueSetState (react-dom.development.js:12789)
    at ReactMapboxGl.push../node_modules/react/cjs/react.development.js.Component.setState (react.development.js:354)
    at r.<anonymous> (map.js:218)
    at r.ot.fire (mapbox-gl.js:700)
    at r._render (mapbox-gl.js:22475)
    at mapbox-gl.js:22489
EnhancedLayer.componentWillMount @ layer-events-hoc.js:263
callComponentWillMount @ react-dom.development.js:13025
mountClassInstance @ react-dom.development.js:13123
updateClassComponent @ react-dom.development.js:14854
beginWork @ react-dom.development.js:15716
performUnitOfWork @ react-dom.development.js:18750
workLoop @ react-dom.development.js:18791
callCallback @ react-dom.development.js:147
invokeGuardedCallbackDev @ react-dom.development.js:196
invokeGuardedCallback @ react-dom.development.js:250
replayUnitOfWork @ react-dom.development.js:17998
renderRoot @ react-dom.development.js:18909
performWorkOnRoot @ react-dom.development.js:19812
performWork @ react-dom.development.js:19722
performSyncWork @ react-dom.development.js:19696
requestWork @ react-dom.development.js:19551
scheduleWork @ react-dom.development.js:19358
enqueueSetState @ react-dom.development.js:12789
push../node_modules/react/cjs/react.development.js.Component.setState @ react.development.js:354
(anonymous) @ map.js:218
ot.fire @ mapbox-gl.js:700
r._render @ mapbox-gl.js:22475
(anonymous) @ mapbox-gl.js:22489
requestAnimationFrame (async)
frame @ mapbox-gl.js:335
r.triggerRepaint @ mapbox-gl.js:22488
r._render @ mapbox-gl.js:22475
(anonymous) @ mapbox-gl.js:22489
requestAnimationFrame (async)
frame @ mapbox-gl.js:335
r.triggerRepaint @ mapbox-gl.js:22488
r._update @ mapbox-gl.js:22445
(anonymous) @ mapbox-gl.js:22124
ot.fire @ mapbox-gl.js:700
ot.fire @ mapbox-gl.js:709
ot.fire @ mapbox-gl.js:709
ot.fire @ mapbox-gl.js:709
i._tileLoaded @ mapbox-gl.js:16915
a @ mapbox-gl.js:15727
fu.receive @ mapbox-gl.js:11640
index.js:1452 The above error occurred in the <EnhancedLayer> component:
    in EnhancedLayer (created by Context.Consumer)
    in MappedComponent (at TransformerLoadMetricDataMap.jsx:297)
 EnhancedLayer.prototype.componentWillMount = function () {
      var map = this.props.map;
      map.on('click', this.id, this.onClick); // <- fails here as map is undefined
      map.on('mouseenter', this.id, this.onMouseEnter);
      map.on('mouseleave', this.id, this.onMouseLeave);
      map.on('mousedown', this.id, this.onMouseDown);
      map.on('touchstart', this.id, this.onTouchStart);
    };

package.json

{
  "name": "xxxx",
  "version": "1.0.0",
  "private": true,
  "dependencies": {
    "@material-ui/core": "^3.2.2",
    "@material-ui/icons": "^3.0.1",
    "@material-ui/lab": "^3.0.0-alpha.20",
    "highcharts": "^6.2.0",
    "lodash": "^4.17.10",
    "luxon": "^1.3.3",
    "mapbox-gl": "^0.51.0",
    "normalizr": "^3.2.4",
    "react": "^16.6.3",
    "react-dom": "^16.6.3",
    "react-jsx-highcharts": "^3.3.0",
    "react-mapbox-gl": "^4.0.2",
    "react-redux": "^5.0.7",
    "react-resize-detector": "^3.1.2",
    "react-scripts": "^2.1.1",
    "react-time-ago": "^3.0.3",
    "react-toggle-switch": "^3.0.4",
    "recompose": "^0.27.1",
    "redux": "^4.0.1",
    "redux-thunk": "^2.3.0",
    "styled-components": "^3.3.2",
    "styled-theming": "^2.2.0",
    "typeface-roboto": "0.0.54"
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test --env=jsdom",
    "eject": "react-scripts eject",
    "flow": "flow",
    "storybook": "start-storybook -p 9009 -s public",
    "build-storybook": "build-storybook -s public"
  },
  "devDependencies": {
    "source-map-explorer": "^1.6.0",
    "@storybook/addons": "^4.0.7",
    "@storybook/addon-actions": "^4.0.7",
    "@storybook/addon-backgrounds": "^4.0.7",
    "@storybook/addon-links": "^4.0.7",
    "@storybook/react": "^4.0.7",
    "cross-env": "^5.2.0",
    "flow-bin": "^0.83.0"
  },
  "browserslist": [
    ">0.2%",
    "not dead",
    "not ie <= 11",
    "not op_mini all"
  ]
}

The error suddenly occurred in our product without we had done any changes to our map component using react-mapbox-gl or the data given to it. So we suspect it could be an issue with an indirect dependency, which proved a bug? in EnhancedLayer component?

mklopets commented 5 years ago

Hi! Sorry for the troubles, I'll check it out shortly.

Could you provide a minimal failing code sample to speed the diagnosing up? And do you know when this was last working for you?

This might be related to the 4.0.0 release, so a late 3.x release might work as a quickfix.

jvskriubakken commented 5 years ago

Hi! Thanks for looking into this! We have setup some example code similar to what is failing by us. However we are not able to reproduce the error :( Example here: https://stackblitz.com/edit/react-zhhbjd?file=Map.js

In our build and deployment history we have found a deploy that works and the next that does not. However there is no changes in our code, or our direct dependencies that could cause this problem (only a few unrelated components where added). However we had not committed the package-lock.json into our project repo. So we suspect that we where hit by breaking changes in transitive dependencies. But what could cause map in the EnhancedLayer component to be undefined?

mklopets commented 5 years ago

I can't see how any other dependencies could break this 😕

As a general suggestion, do commit in your lockfiles to avoid some headaches... But as for this issue, I don't know how to debug it, really. Could add in a check for whether map is undefined... But that case should be avoided by the Map component not rendering its children unless the map is initialized.

jvskriubakken commented 5 years ago

:( I'm trying to follow the code... and where map is set as a property into EnchancedLayer: EnchancedLayer is wrapped by function layerMouseTouchEvents, which again is given as input to withMap which passes the map property onto EnchancedLayer. And the map is received from MapContext.Consumer?

jvskriubakken commented 5 years ago

Hi again,

I see that react-mapbox-gl depends on supercluster@3.0.2 while mapbox-gl depdens on supercluster@4.1.1. Could this have any cause in this? We are using create-react-app - do you know if cra/babel/webpack manages two versions into same compiled js?

react-mapbox-gl dependency:

├─┬ react-mapbox-gl@4.0.2
│ ├─┬ @turf/bbox@4.7.3
│ │ └── @turf/meta@4.7.4
│ ├── @turf/helpers@4.7.3
│ ├── deep-equal@1.0.1 deduped
│ └─┬ supercluster@3.0.2
│   └── kdbush@1.0.1

mapbox-gl dependency:

├─┬ mapbox-gl@0.51.0
│ ├── @mapbox/geojson-types@1.0.2
│ ├── @mapbox/jsonlint-lines-primitives@2.0.2
│ ├── @mapbox/mapbox-gl-supported@1.4.0
│ ├── @mapbox/point-geometry@0.1.0
│ ├── @mapbox/tiny-sdf@1.1.0
│ ├── @mapbox/unitbezier@0.0.0
│ ├─┬ @mapbox/vector-tile@1.3.1
│ │ └── @mapbox/point-geometry@0.1.0 deduped
│ ├── @mapbox/whoots-js@3.1.0
│ ├── csscolorparser@1.0.3
│ ├── earcut@2.1.3
│ ├── esm@3.0.84
│ ├─┬ geojson-rewind@0.3.1
│ │ ├─┬ @mapbox/geojson-area@0.2.2
│ │ │ └── wgs84@0.0.0
│ │ ├─┬ concat-stream@1.6.2
│ │ │ ├── buffer-from@1.1.1
│ │ │ ├── inherits@2.0.3 deduped
│ │ │ ├── readable-stream@2.3.6 deduped
│ │ │ └── typedarray@0.0.6
│ │ ├── minimist@1.2.0
│ │ └─┬ sharkdown@0.1.0
│ │   ├─┬ cardinal@0.4.4
│ │   │ ├── ansicolors@0.2.1
│ │   │ └─┬ redeyed@0.4.4
│ │   │   └── esprima@1.0.4
│ │   ├── expect.js@0.2.0
│ │   ├── minimist@0.0.5
│ │   ├─┬ split@0.2.10
│ │   │ └── through@2.3.8 deduped
│ │   ├─┬ stream-spigot@2.1.2
│ │   │ └─┬ readable-stream@1.1.14
│ │   │   ├── core-util-is@1.0.2 deduped
│ │   │   ├── inherits@2.0.3 deduped
│ │   │   ├── isarray@0.0.1
│ │   │   └── string_decoder@0.10.31
│ │   └── through@2.3.8 deduped
│ ├── geojson-vt@3.2.1
│ ├── gl-matrix@2.8.1
│ ├── grid-index@1.0.0
│ ├── minimist@0.0.8
│ ├── murmurhash-js@1.0.0
│ ├─┬ pbf@3.1.0
│ │ ├── ieee754@1.1.12
│ │ └─┬ resolve-protobuf-schema@2.1.0
│ │   └── protocol-buffers-schema@3.3.2
│ ├── potpack@1.0.1
│ ├── quickselect@1.1.1
│ ├── rw@1.3.3
│ ├─┬ supercluster@4.1.1
│ │ └── kdbush@2.0.1
│ ├── tinyqueue@1.2.3
│ └─┬ vt-pbf@3.1.1
│   ├── @mapbox/point-geometry@0.1.0 deduped
│   ├── @mapbox/vector-tile@1.3.1 deduped
│   └── pbf@3.1.0 deduped
jvskriubakken commented 5 years ago

Hi again, I'm trying to debug the problem my self, and in context.js the map is undefined:

context js 22

While earlier in the call stack map is defined:

map js 218

but I have too little react knowledge to to understand where it get lost from that point to later... does this help you? Or is it any debugging hints you could give me?

jvskriubakken commented 5 years ago

I figured out that state.map is also undefined at the time when the MapContext.Provider is created:

map mapcontextprovider
mklopets commented 5 years ago

Sorry for the delay.

The supercluster dep discrepancy should be fixed, but it's probably unrelated to this issue. In general, webpack should be able to properly resolve multiple versions of the same package.

src/map.tsx shouldn't render out the Layer if its state.ready = false.

I understand this is time critical for you – please try using v3.9.2 for the time being – I believe it won't have this bug.

I now have time to properly look into it. I know there are some lacking typings here that could cause it, but couldn't confirm my specific suspicions at first glance.

jvskriubakken commented 5 years ago

Thanks a lot for your time! Downgrading to v3.9.2 solved the issue!

mklopets commented 5 years ago

No problemo.

To confirm, in your real code, you have a Layer and a Feature inside the react-mapbox-gl Map component? And the Layer is conditionally rendered?

jvskriubakken commented 5 years ago

This is my map component:

<MapBox
               style={'mapbox://styles/mapbox/dark-v9'}
               logoPosition="bottom-right"
               doubleClickZoom={false}
               center={center}
               fitBounds={fitBounds}
               containerStyle={{
                 height: "100%",
                 width: "100%"
               }}
               onStyleLoad={this.handleStyleLoad}
            >
               {substationTooltip &&
               <Popup
                  onMouseEnter={() => this.cancelHideSubstationTooltipTimer()}
                  onMouseLeave={() => this.startHideSubstationTooltipTimer()}
                  coordinates={substationTooltip.coordinates}
                  offset={15}
               >
                  <List disablePadding={true}>
                     {substationTooltip.loadMetrics.map((metric, index) => {
                        return (
                           <ListItem
                              selected={selectedTransformer === metric.key.ownerId}
                              button
                              key={`${index}${metric.key.ownerId}`}
                              onClick={() => selectTransformer(metric.key.ownerId)}
                           >
                              <ListItemIcon
                                 style={{
                                    color: metric.peakPercentage >= 100 ? theme.asset.transformer.warningColor : theme.asset.transformer.defaultColor,
                                 }}
                              >
                                 <Lens/>
                              </ListItemIcon>
                              <ListItemText primary={substationTooltip.transformers[index].name}/>
                           </ListItem>
                        )
                     })}
                  </List>
               </Popup>
               }
               {loadMetricData &&
               <Layer type="circle"
                      id="substation-layer"
                      paint={{
                         'circle-color': {
                            'property': 'peakPercentage',
                            'type': 'exponential',
                            'default': theme.asset.substation.defaultColor,
                            'stops': [
                               [0, theme.asset.substation.defaultColor],
                               [99, theme.asset.substation.defaultColor],
                               [100, theme.asset.substation.warningColor],
                               [300, theme.asset.substation.warningColor]
                            ]
                         },

                         'circle-stroke-color': {
                            'property': 'substationId',
                            'type': 'categorical',
                            'default': 'transparent',
                            'stops': [
                               [`${selectedSubstationIdOrZero}`, theme.map.selection.borderColor]
                            ]
                         },

                         'circle-stroke-width': 2,
                         'circle-radius': {
                            'property': 'peakPercentage',
                            'type': 'exponential',
                            'default': 5,
                            'stops': [
                               [0, 5],
                               [100, 10],
                               [200, 15]
                            ]
                         },
                      }}>

                  {substations.length > 0 &&
                     substations.map((substation) => {
                        const substationCoordinates = geometryObjectToLngLat(substation.geometryObject);
                        const loadMetrics = loadMetricData.loadMetrics
                           .filter(lm => substation.transformers.includes(lm.key.ownerId));

                        const loadMetricWithHighestPeakPercentage = loadMetrics.reduce((max, loadMetric) => max.peakPercentage > loadMetric.peakPercentage ? max : loadMetric);

                        const featureProperties = {
                           ...loadMetricWithHighestPeakPercentage,
                           substationId: substation.id
                        };

                        return (
                           <Feature
                              coordinates={substationCoordinates}
                              properties={(featureProperties)}
                              id={substation.id}
                              key={substation.id}
                              transformer={loadMetricWithHighestPeakPercentage.key.ownerId}
                              onMouseEnter={(event) => {
                                 event.map._canvas.style.cursor = 'pointer';
                                 this.handleMouseEnter(
                                    substationCoordinates,
                                    substation.id
                                 );
                                 this.cancelHideSubstationTooltipTimer();
                              }}
                              onMouseLeave={(event) => {
                                 event.map._canvas.style.cursor = '';
                                 this.startHideSubstationTooltipTimer();
                              }}
                              onClick={() => selectTransformer(loadMetricWithHighestPeakPercentage.key.ownerId)}
                           />
                        )
                     })}
               </Layer>
               }
            </MapBox>
jvskriubakken commented 5 years ago

No problemo.

To confirm, in your real code, you have a Layer and a Feature inside the react-mapbox-gl Map component? And the Layer is conditionally rendered?

Yes, I think it's correct answer you yes on this .

mklopets commented 5 years ago

could you also provide your handleStyleLoad method?

jvskriubakken commented 5 years ago
handleStyleLoad = map => {
      map.resize();
   };
jvskriubakken commented 5 years ago

I've sent you the whole component in your email registered at github.

mklopets commented 5 years ago

Thanks! I'll take a look.

daniel-hauser commented 5 years ago

@mklopets, did you have any progress with it? if not, can you or @jvskriubakken send it to me as well?

Stouffi commented 5 years ago

I ran into the same issue (similar stack strace with layer-events-hoc.js:263 Uncaught TypeError: Cannot read property 'on' of undefined appearing after upgrading from 3.9.2 to 4+) and this seems to be related to #691. To check that my issue was related to #691, I used MapContext.Consumer in the crashing Map component and the value provided was always undefined. Then I checked all my imports from react-mapbox-gl throughout my project and I realised that some were using paths like react-mapbox-gl/lib/Marker. After fixing them to use only from 'react-mapbox-gl', The MapContext.Consumer provided the map as expected and no more undefined were logged. This has also fixed the runtime error.