Deck.gl: "TypeError: Cannot call a class as a function" when using layers the React way

Created on 20 Apr 2019  路  10Comments  路  Source: visgl/deck.gl

Description

When I'm trying to use Deck.gl along with ReactMapGl to render some layers onto a map, I'm getting this error:

scatterplot-layer.js:22 Uncaught TypeError: Cannot call a class as a function
    at _classCallCheck (scatterplot-layer.js:22)
    at ScatterplotLayer (scatterplot-layer.js:120)
    at renderWithHooks (react-dom.development.js:12839)
[...]

Repro Steps

Check out this code sandbox: https://codesandbox.io/s/o7356m7qy5?fontsize=14

There, in the code editor, you can enable two different ways ScatterplotLayer is rendered via another component called Layer:

  1. with createElement
  const Layer = ({ data }) => (
    <ScatterplotLayer
      data={data}
      radiusScale={30}
      radiusMinPixels={0.5}
      getPosition={getPosition}
      getFillColor={[0, 140, 0]}
    />
  );

  return (
    <DeckGL controller={true} viewState={viewState}>
      <ReactMapGL mapboxApiAccessToken={mapApiToken} />

      <Layer data={data} />

    </DeckGL>
  );
  1. called as function
  const Layer = ({ data }) => (
    <ScatterplotLayer
      data={data}
      radiusScale={30}
      radiusMinPixels={0.5}
      getPosition={getPosition}
      getFillColor={[0, 140, 0]}
    />
  );

  return (
    <DeckGL controller={true} viewState={viewState}>
      <ReactMapGL mapboxApiAccessToken={mapApiToken} />

      {Layer({ data })}

    </DeckGL>
  );

To my knowledge, that's because deck.gl isn't extending React.Component at the base of the inheritance tree. As a result, React may not determine whether it is rendering a class component or a functional one.

This very behavior is documented in this SO question in a slightly different scenario:

https://stackoverflow.com/questions/38481857/getting-cannot-call-a-class-as-a-function-in-my-react-project

Environment (please complete the following information):

  • Framework Version: deck.gl 6.4.9 as well as 7.0.0-rc.1
  • Browser Version: Chrome 73.0.3683.103
  • OS: Mac OS X 10.14.4 (18E226)

Logs

luma.gl: WebGL debug support imported
scatterplot-layer.js:22 Uncaught TypeError: Cannot call a class as a function
    at _classCallCheck (scatterplot-layer.js:22)
    at ScatterplotLayer (scatterplot-layer.js:120)
    at renderWithHooks (react-dom.development.js:12839)
    at mountIndeterminateComponent (react-dom.development.js:14816)
    at beginWork (react-dom.development.js:15421)
    at performUnitOfWork (react-dom.development.js:19108)
    at workLoop (react-dom.development.js:19148)
    at HTMLUnknownElement.callCallback (react-dom.development.js:149)
    at Object.invokeGuardedCallbackDev (react-dom.development.js:199)
    at invokeGuardedCallback (react-dom.development.js:256)
    at replayUnitOfWork (react-dom.development.js:18374)
    at renderRoot (react-dom.development.js:19264)
    at performWorkOnRoot (react-dom.development.js:20138)
    at performWork (react-dom.development.js:20050)
    at performSyncWork (react-dom.development.js:20024)
    at requestWork (react-dom.development.js:19893)
    at scheduleWork (react-dom.development.js:19707)
    at Object.enqueueSetState (react-dom.development.js:11141)
    at DeckGL.Component.setState (react.development.js:335)
    at DeckGL._onResize (deckgl.js:162)
    at Deck._updateCanvasSize (deck.js:400)
    at Deck._setGLContext (deck.js:611)
    at Deck._onRendererInitialized (deck.js:642)
    at AnimationLoop.onInitialize (animation-loop.js:234)
    at eval (animation-loop.js:170)
_classCallCheck @ scatterplot-layer.js:22
ScatterplotLayer @ scatterplot-layer.js:120
renderWithHooks @ react-dom.development.js:12839
mountIndeterminateComponent @ react-dom.development.js:14816
beginWork @ react-dom.development.js:15421
performUnitOfWork @ react-dom.development.js:19108
workLoop @ react-dom.development.js:19148
callCallback @ react-dom.development.js:149
invokeGuardedCallbackDev @ react-dom.development.js:199
invokeGuardedCallback @ react-dom.development.js:256
replayUnitOfWork @ react-dom.development.js:18374
renderRoot @ react-dom.development.js:19264
performWorkOnRoot @ react-dom.development.js:20138
performWork @ react-dom.development.js:20050
performSyncWork @ react-dom.development.js:20024
requestWork @ react-dom.development.js:19893
scheduleWork @ react-dom.development.js:19707
enqueueSetState @ react-dom.development.js:11141
Component.setState @ react.development.js:335
_onResize @ deckgl.js:162
_updateCanvasSize @ deck.js:400
_setGLContext @ deck.js:611
_onRendererInitialized @ deck.js:642
onInitialize @ animation-loop.js:234
(anonymous) @ animation-loop.js:170
Promise.then (async)
start @ animation-loop.js:147
Deck @ deck.js:159
componentDidMount @ deckgl.js:78
commitLifeCycles @ react-dom.development.js:17130
commitAllLifeCycles @ react-dom.development.js:18532
callCallback @ react-dom.development.js:149
invokeGuardedCallbackDev @ react-dom.development.js:199
invokeGuardedCallback @ react-dom.development.js:256
commitRoot @ react-dom.development.js:18744
(anonymous) @ react-dom.development.js:20214
unstable_runWithPriority @ scheduler.development.js:255
completeRoot @ react-dom.development.js:20213
performWorkOnRoot @ react-dom.development.js:20142
performWork @ react-dom.development.js:20050
performSyncWork @ react-dom.development.js:20024
requestWork @ react-dom.development.js:19893
scheduleWork @ react-dom.development.js:19707
scheduleRootUpdate @ react-dom.development.js:20368
updateContainerAtExpirationTime @ react-dom.development.js:20396
updateContainer @ react-dom.development.js:20453
ReactRoot.render @ react-dom.development.js:20749
(anonymous) @ react-dom.development.js:20886
unbatchedUpdates @ react-dom.development.js:20255
legacyRenderSubtreeIntoContainer @ react-dom.development.js:20882
render @ react-dom.development.js:20951
evaluate @ index.js? [sm]:48
ue @ eval.js:45
value @ transpiled-module.ts:968
value @ manager.ts:355
value @ manager.ts:326
(anonymous) @ compile.ts:579
g @ runtime.js:62
(anonymous) @ runtime.js:288
e.(anonymous function) @ runtime.js:114
t @ asyncToGenerator.js:3
u @ asyncToGenerator.js:25
Promise.then (async)
t @ asyncToGenerator.js:13
u @ asyncToGenerator.js:25
Promise.then (async)
t @ asyncToGenerator.js:13
u @ asyncToGenerator.js:25
Promise.then (async)
t @ asyncToGenerator.js:13
u @ asyncToGenerator.js:25
Promise.then (async)
t @ asyncToGenerator.js:13
u @ asyncToGenerator.js:25
Promise.then (async)
t @ asyncToGenerator.js:13
u @ asyncToGenerator.js:25
(anonymous) @ asyncToGenerator.js:32
(anonymous) @ asyncToGenerator.js:21
ie @ compile.ts:387
oe @ compile.ts:387
(anonymous) @ compile.ts:731
g @ runtime.js:62
(anonymous) @ runtime.js:288
e.(anonymous function) @ runtime.js:114
t @ asyncToGenerator.js:3
u @ asyncToGenerator.js:25
(anonymous) @ asyncToGenerator.js:32
(anonymous) @ asyncToGenerator.js:21
de @ compile.ts:726
ue @ compile.ts:726
pe @ compile.ts:748
(anonymous) @ index.js:51
g @ runtime.js:62
(anonymous) @ runtime.js:288
e.(anonymous function) @ runtime.js:114
t @ asyncToGenerator.js:3
u @ asyncToGenerator.js:25
(anonymous) @ asyncToGenerator.js:32
(anonymous) @ asyncToGenerator.js:21
(anonymous) @ index.js:40
(anonymous) @ codesandbox.es5.js:410
D @ codesandbox.es5.js:408
k @ codesandbox.es5.js:425
Show 31 more frames
proxyConsole.js:72 The above error occurred in the <ScatterplotLayer> component:
    in ScatterplotLayer (created by Layer)
    in Layer (created by Map)
    in div (created by DeckGL)
    in div (created by DeckGL)
    in DeckGL (created by Map)
    in Map

Consider adding an error boundary to your tree to customize error handling behavior.
Visit https://fb.me/react-error-boundaries to learn more about error boundaries.
console.(anonymous function) @ proxyConsole.js:72
logCapturedError @ react-dom.development.js:16913
logError @ react-dom.development.js:16949
update.callback @ react-dom.development.js:17861
callCallback @ react-dom.development.js:16229
commitUpdateEffects @ react-dom.development.js:16268
commitUpdateQueue @ react-dom.development.js:16259
commitLifeCycles @ react-dom.development.js:17179
commitAllLifeCycles @ react-dom.development.js:18532
callCallback @ react-dom.development.js:149
invokeGuardedCallbackDev @ react-dom.development.js:199
invokeGuardedCallback @ react-dom.development.js:256
commitRoot @ react-dom.development.js:18744
(anonymous) @ react-dom.development.js:20214
unstable_runWithPriority @ scheduler.development.js:255
completeRoot @ react-dom.development.js:20213
performWorkOnRoot @ react-dom.development.js:20142
performWork @ react-dom.development.js:20050
performSyncWork @ react-dom.development.js:20024
requestWork @ react-dom.development.js:19893
scheduleWork @ react-dom.development.js:19707
enqueueSetState @ react-dom.development.js:11141
Component.setState @ react.development.js:335
_onResize @ deckgl.js:162
_updateCanvasSize @ deck.js:400
_setGLContext @ deck.js:611
_onRendererInitialized @ deck.js:642
onInitialize @ animation-loop.js:234
(anonymous) @ animation-loop.js:170
Promise.then (async)
start @ animation-loop.js:147
Deck @ deck.js:159
componentDidMount @ deckgl.js:78
commitLifeCycles @ react-dom.development.js:17130
commitAllLifeCycles @ react-dom.development.js:18532
callCallback @ react-dom.development.js:149
invokeGuardedCallbackDev @ react-dom.development.js:199
invokeGuardedCallback @ react-dom.development.js:256
commitRoot @ react-dom.development.js:18744
(anonymous) @ react-dom.development.js:20214
unstable_runWithPriority @ scheduler.development.js:255
completeRoot @ react-dom.development.js:20213
performWorkOnRoot @ react-dom.development.js:20142
performWork @ react-dom.development.js:20050
performSyncWork @ react-dom.development.js:20024
requestWork @ react-dom.development.js:19893
scheduleWork @ react-dom.development.js:19707
scheduleRootUpdate @ react-dom.development.js:20368
updateContainerAtExpirationTime @ react-dom.development.js:20396
updateContainer @ react-dom.development.js:20453
ReactRoot.render @ react-dom.development.js:20749
(anonymous) @ react-dom.development.js:20886
unbatchedUpdates @ react-dom.development.js:20255
legacyRenderSubtreeIntoContainer @ react-dom.development.js:20882
render @ react-dom.development.js:20951
evaluate @ index.js? [sm]:48
ue @ eval.js:45
value @ transpiled-module.ts:968
value @ manager.ts:355
value @ manager.ts:326
(anonymous) @ compile.ts:579
g @ runtime.js:62
(anonymous) @ runtime.js:288
e.(anonymous function) @ runtime.js:114
t @ asyncToGenerator.js:3
u @ asyncToGenerator.js:25
Promise.then (async)
t @ asyncToGenerator.js:13
u @ asyncToGenerator.js:25
Promise.then (async)
t @ asyncToGenerator.js:13
u @ asyncToGenerator.js:25
Promise.then (async)
t @ asyncToGenerator.js:13
u @ asyncToGenerator.js:25
Promise.then (async)
t @ asyncToGenerator.js:13
u @ asyncToGenerator.js:25
Promise.then (async)
t @ asyncToGenerator.js:13
u @ asyncToGenerator.js:25
(anonymous) @ asyncToGenerator.js:32
(anonymous) @ asyncToGenerator.js:21
ie @ compile.ts:387
oe @ compile.ts:387
(anonymous) @ compile.ts:731
g @ runtime.js:62
(anonymous) @ runtime.js:288
e.(anonymous function) @ runtime.js:114
t @ asyncToGenerator.js:3
u @ asyncToGenerator.js:25
(anonymous) @ asyncToGenerator.js:32
(anonymous) @ asyncToGenerator.js:21
de @ compile.ts:726
ue @ compile.ts:726
pe @ compile.ts:748
(anonymous) @ index.js:51
g @ runtime.js:62
(anonymous) @ runtime.js:288
e.(anonymous function) @ runtime.js:114
t @ asyncToGenerator.js:3
u @ asyncToGenerator.js:25
(anonymous) @ asyncToGenerator.js:32
(anonymous) @ asyncToGenerator.js:21
(anonymous) @ index.js:40
(anonymous) @ codesandbox.es5.js:410
D @ codesandbox.es5.js:408
k @ codesandbox.es5.js:425
Show 35 more frames
scatterplot-layer.js:22 Uncaught (in promise) TypeError: Cannot call a class as a function
    at _classCallCheck (scatterplot-layer.js:22)
    at ScatterplotLayer (scatterplot-layer.js:120)
    at renderWithHooks (react-dom.development.js:12839)
    at mountIndeterminateComponent (react-dom.development.js:14816)
    at beginWork (react-dom.development.js:15421)
    at performUnitOfWork (react-dom.development.js:19108)
    at workLoop (react-dom.development.js:19148)
    at renderRoot (react-dom.development.js:19231)
    at performWorkOnRoot (react-dom.development.js:20138)
    at performWork (react-dom.development.js:20050)
    at performSyncWork (react-dom.development.js:20024)
    at requestWork (react-dom.development.js:19893)
    at scheduleWork (react-dom.development.js:19707)
    at Object.enqueueSetState (react-dom.development.js:11141)
    at DeckGL.Component.setState (react.development.js:335)
    at DeckGL._onResize (deckgl.js:162)
    at Deck._updateCanvasSize (deck.js:400)
    at Deck._setGLContext (deck.js:611)
    at Deck._onRendererInitialized (deck.js:642)
    at AnimationLoop.onInitialize (animation-loop.js:234)
    at eval (animation-loop.js:170)
bug

Most helpful comment

I managed my needs insofar as that I'm using the layers prop instead in order to dynamically build my list of layers.

@Pessimistress CompositeLayer may come in handy when combining multiple layers, sure. For my use case, I think it's not relevant right now.

馃A note about this possible pitfall in the docs would be great though.

All 10 comments

@jhohlfeld

DeckGL takes layers as a prop, not as its dom children, as the deck.gl and its layers are react independent.

    <DeckGL 
      layer={[new ScatterplotLayer({ data })]} 
      controller={true} viewState={viewState}
    >
      <ReactMapGL mapboxApiAccessToken={mapApiToken} />
    </DeckGL>
  );

Here is the starting point of using DeckGL with react

You are correct, the layers do not extend React.Component, so React does not know how to render JSX layers. When DeckGL is rendered, it intercepts the list of child components and check if any of them inherits the deck.gl Layer class. These children, if any, are removed from the React tree and assigned to Deck's layers prop instead.

Other than the the DeckGL component, nothing in deck.gl is React dependent. Layers are just es6 classes that are shared by both React and non-React scenarios, therefore the support for alternative React patterns might be limited.

@xintongxia sorry but I doubt your claim - adding layers as JSX children is a widely suggested practice in the deck.gl docs. Even in your upper link:

http://deck.gl/#/documentation/getting-started/using-with-react?section=using-jsx-with-deck-gl-layers

 render() {
    return (
      <DeckGL {...viewState}>
        <LineLayer id="line-layer" data={data} />
      </DeckGL>
    );
  }

@Pessimistress and the issue only shows itself when you add intermediate JSX components. Normally, an HOC (higher order component) should be feasible in this context, even if deck.gl applies some of its own magic. However, if you're propagating JSX, you should support it 100%, right?

@jhohlfeld DeckGL uses React.Children.forEach to walk through its child nodes. In your first usage sample, <Layer data={data} /> is not evaluated until after DeckGL's render places it into the virtual DOM tree, so the parent component has no visibility into what it contains.

If your intention is to manage multiple layers with a cleaner interface, I recommend creating a custom composite layer, something like:

import {CompositeLayer} from '@deck.gl/core';

class MyLayer extends CompositeLayer {
  renderLayers() {
    return new ScatterplotLayer({
      data: this.props.data,
      radiusScale: 30,
      radiusMinPixels: 0.5,
      getPosition: getPosition,
      getFillColor: [0, 140, 0]
    });
  }
}

Because MyLayer extends the deck.gl base Layer class, you can then do

render() {
  return (
    <DeckGL controller={true} viewState={viewState}>
      <ReactMapGL mapboxApiAccessToken={mapApiToken} />
      <MyLayer data={data} />
    </DeckGL>
  );
}

I managed my needs insofar as that I'm using the layers prop instead in order to dynamically build my list of layers.

@Pessimistress CompositeLayer may come in handy when combining multiple layers, sure. For my use case, I think it's not relevant right now.

馃A note about this possible pitfall in the docs would be great though.

I was going to use the CompositeLayer, then I noticed you actually need a class component for this.
Since I'm using recoil which requires hooks I cannot use that solution with it.
The reason I'm forced to do it this way is I need to wrap only the Layer into a Suspense component so that the whole thing doesn't flash everytime I make an API call.
Is there a way to still make it work with those constraints ?

@skflowne
The way I went around this constraint was by using the RecoilValueLoadable, an escape hatch from suspense for these types of situations. Instead of wrapping the layer in Suspense you have to check the Loadable.state for when your data has loaded

@BertCh Thanks, yes I ended up doing this, it works fine

@jhohlfeld DeckGL uses React.Children.forEach to walk through its child nodes. In your first usage sample, <Layer data={data} /> is not evaluated until after DeckGL's render places it into the virtual DOM tree, so the parent component has no visibility into what it contains.

If your intention is to manage multiple layers with a cleaner interface, I recommend creating a custom composite layer, something like:

import {CompositeLayer} from '@deck.gl/core';

class MyLayer extends CompositeLayer {
  renderLayers() {
    return new ScatterplotLayer({
      data: this.props.data,
      radiusScale: 30,
      radiusMinPixels: 0.5,
      getPosition: getPosition,
      getFillColor: [0, 140, 0]
    });
  }
}

Because MyLayer extends the deck.gl base Layer class, you can then do

render() {
  return (
    <DeckGL controller={true} viewState={viewState}>
      <ReactMapGL mapboxApiAccessToken={mapApiToken} />
      <MyLayer data={data} />
    </DeckGL>
  );
}

@jhohlfeld DeckGL uses React.Children.forEach to walk through its child nodes. In your first usage sample, <Layer data={data} /> is not evaluated until after DeckGL's render places it into the virtual DOM tree, so the parent component has no visibility into what it contains.

If your intention is to manage multiple layers with a cleaner interface, I recommend creating a custom composite layer, something like:

import {CompositeLayer} from '@deck.gl/core';

class MyLayer extends CompositeLayer {
  renderLayers() {
    return new ScatterplotLayer({
      data: this.props.data,
      radiusScale: 30,
      radiusMinPixels: 0.5,
      getPosition: getPosition,
      getFillColor: [0, 140, 0]
    });
  }
}

Because MyLayer extends the deck.gl base Layer class, you can then do

render() {
  return (
    <DeckGL controller={true} viewState={viewState}>
      <ReactMapGL mapboxApiAccessToken={mapApiToken} />
      <MyLayer data={data} />
    </DeckGL>
  );
}

This should be mentioned in the docs

Was this page helpful?
0 / 5 - 0 ratings

Related issues

mathieudelvaux picture mathieudelvaux  路  4Comments

Dieegho picture Dieegho  路  3Comments

TareqAlqutami picture TareqAlqutami  路  3Comments

jacklam718 picture jacklam718  路  4Comments

mayteio picture mayteio  路  3Comments