• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

visgl / deck.gl / 30759731363

02 Aug 2026 05:49PM UTC coverage: 83.077%. Remained the same
30759731363

push

github

web-flow
feat(mesh-layers): port SimpleMeshLayer to WebGPU (#10485)

8293 of 10488 branches covered (79.07%)

Branch coverage included in aggregate %.

10 of 11 new or added lines in 5 files covered. (90.91%)

12 existing lines in 2 files now uncovered.

14643 of 17120 relevant lines covered (85.53%)

18722.26 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

85.82
/modules/react/src/deckgl.ts
1
// deck.gl
2
// SPDX-License-Identifier: MIT
3
// Copyright (c) vis.gl contributors
4

5
import * as React from 'react';
2!
6
import {createElement, useRef, useState, useMemo, useEffect, useImperativeHandle} from 'react';
2✔
7
import {Deck} from '@deck.gl/core';
8
import useIsomorphicLayoutEffect from './utils/use-isomorphic-layout-effect';
9

10
import extractJSXLayers, {DeckGLRenderCallback} from './utils/extract-jsx-layers';
11
import positionChildrenUnderViews from './utils/position-children-under-views';
12
import extractStyles from './utils/extract-styles';
13

14
import type {DeckGLContextValue} from './utils/deckgl-context';
15
import type {DeckProps, View, Viewport} from '@deck.gl/core';
16

17
export type ViewOrViews = View | View[] | null;
18

19
/* eslint-disable max-statements, accessor-pairs */
20
type DeckInstanceRef<ViewsT extends ViewOrViews> = {
21
  deck?: Deck<ViewsT>;
22
  redrawReason?: string | null;
23
  lastRenderedViewports?: Viewport[];
24
  viewStateUpdateRequested?: any;
25
  interactionStateUpdateRequested?: any;
26
  forceUpdate: () => void;
27
  version: number;
28
  control: React.ReactHTMLElement<HTMLElement> | null;
29
};
30

31
// Remove prop types in the base Deck class that support externally supplied canvas/WebGLContext
32
/** DeckGL React component props */
33
export type DeckGLProps<ViewsT extends ViewOrViews = null> = Omit<
34
  DeckProps<ViewsT>,
35
  'width' | 'height' | 'gl' | 'parent' | 'canvas' | '_customRender'
36
> & {
37
  Deck?: typeof Deck;
38
  width?: string | number;
39
  height?: string | number;
40
  children?: React.ReactNode | DeckGLRenderCallback;
41
  ref?: React.Ref<DeckGLRef<ViewsT>>;
42
  ContextProvider?: React.Context<DeckGLContextValue>['Provider'];
43
};
44

45
export type DeckGLRef<ViewsT extends ViewOrViews = null> = {
46
  deck?: Deck<ViewsT>;
47
  pickObjectAsync: Deck['pickObjectAsync'];
48
  pickObjectsAsync: Deck['pickObjectsAsync'];
49
  pickObject: Deck['pickObject'];
50
  pickObjects: Deck['pickObjects'];
51
  pickMultipleObjects: Deck['pickMultipleObjects'];
52
};
53

54
function getRefHandles<ViewsT extends ViewOrViews>(
55
  thisRef: DeckInstanceRef<ViewsT>
56
): DeckGLRef<ViewsT> {
57
  return {
7✔
58
    get deck() {
59
      return thisRef.deck;
26✔
60
    },
61
    // The following method can only be called after ref is available, by which point deck is defined in useEffect
62
    pickObjectAsync: opts => thisRef.deck!.pickObjectAsync(opts),
×
63
    pickObjectsAsync: opts => thisRef.deck!.pickObjectsAsync(opts),
×
64
    pickObject: opts => thisRef.deck!.pickObject(opts),
×
65
    pickMultipleObjects: opts => thisRef.deck!.pickMultipleObjects(opts),
×
66
    pickObjects: opts => thisRef.deck!.pickObjects(opts)
×
67
  };
68
}
69

70
function redrawDeck(thisRef: DeckInstanceRef<any>) {
71
  if (thisRef.redrawReason) {
39✔
72
    // Only redraw if we have received a dirty flag
73
    // @ts-expect-error accessing protected method
74
    thisRef.deck._drawLayers(thisRef.redrawReason);
22✔
75
    thisRef.redrawReason = null;
22✔
76
  }
77
}
78

79
// luma.gl initially gives a detached canvas a small placeholder size. Do not mount a basemap
80
// against that temporary viewport: MapLibre would initialize its own canvas at 1 x 1 pixels.
81
function deckSizeMatchesContainer(
82
  thisRef: DeckInstanceRef<any>,
83
  container: HTMLElement | null
84
): boolean {
85
  const deck = thisRef.deck;
3✔
86
  return Boolean(
3✔
87
    deck &&
11✔
88
      container &&
89
      deck.width === container.clientWidth &&
90
      deck.height === container.clientHeight
91
  );
92
}
93

94
function createDeckInstance<ViewsT extends ViewOrViews>(
95
  thisRef: DeckInstanceRef<ViewsT>,
96
  DeckClass: typeof Deck,
97
  props: DeckProps<ViewsT>
98
): Deck<ViewsT> {
99
  const deck = new DeckClass({
11✔
100
    ...props,
101
    // Keep one authoritative render callback for both backends. Deck calls `_customRender` from
102
    // its animation loop whenever its viewport or layers become dirty; this is also the point
103
    // where React children must be synchronized with the viewport used to draw those layers.
104
    _customRender: redrawReason => {
105
      thisRef.redrawReason = redrawReason;
22✔
106
      // `deviceProps` describes the requested adapter, not necessarily the adapter that was
107
      // initialized. Read the resolved device here, after Deck's animation loop has initialized
108
      // it, so fallback from WebGPU to WebGL keeps the WebGL synchronization path.
109
      // @ts-expect-error accessing protected device
110
      const isWebGPU = deck.device?.type === 'webgpu';
22✔
111

112
      const viewports = deck.getViewports();
22✔
113
      if (thisRef.lastRenderedViewports !== viewports) {
22✔
114
        // Do not initialize a map against the detached WebGPU canvas's temporary 1 x 1 viewport.
115
        // The next resize invalidates Deck again and repeats this callback with the final size.
116
        if (!isWebGPU || deckSizeMatchesContainer(thisRef, props.parent || null)) {
13!
117
          thisRef.forceUpdate();
12✔
118
        }
119

120
        if (!isWebGPU) {
13✔
121
          // WebGL may defer drawing until React's layout effect so its DOM children and canvas
122
          // appear in the same frame. Keep master behavior unchanged for the existing backend.
123
          return;
10✔
124
        }
125
      }
126

127
      // WebGPU's current canvas texture is valid only for this animation frame. Draw now, even
128
      // when React still has a pending viewport update; waiting for a layout effect would reuse
129
      // an expired texture. React children catch up through the forceUpdate scheduled above.
130
      redrawDeck(thisRef);
12✔
131
    }
132
  });
133
  return deck;
11✔
134
}
135

136
function DeckGLWithRef<ViewsT extends ViewOrViews = null>(
137
  props: DeckGLProps<ViewsT>,
138
  ref: React.Ref<DeckGLRef<ViewsT>>
139
) {
140
  // A mechanism to force redraw
141
  const [version, setVersion] = useState(0);
27✔
142
  // A reference to persistent states
143
  const _thisRef = useRef<DeckInstanceRef<ViewsT>>({
27✔
144
    control: null,
145
    version,
146
    forceUpdate: () => setVersion(v => v + 1)
12✔
147
  });
148
  const thisRef = _thisRef.current;
27✔
149
  // DOM refs
150
  const containerRef = useRef(null);
27✔
151
  const canvasRef = useRef(null);
27✔
152

153
  // extract any deck.gl layers masquerading as react elements from props.children
154
  const jsxProps = useMemo(
27✔
155
    () => extractJSXLayers(props),
27✔
156
    [props.layers, props.views, props.children]
157
  );
158

159
  // Callbacks
160
  let inRender = true;
27✔
161

162
  const handleViewStateChange: DeckProps<ViewsT>['onViewStateChange'] = params => {
27✔
163
    if (inRender && props.viewState) {
6!
164
      // Callback may invoke a state update. Defer callback to after render() to avoid React error
165
      // In React StrictMode, render is executed twice and useEffect/useLayoutEffect is executed once
166
      // Store deferred parameters in ref so that we can access it in another render
UNCOV
167
      thisRef.viewStateUpdateRequested = params;
×
UNCOV
168
      return null;
×
169
    }
170
    thisRef.viewStateUpdateRequested = null;
6✔
171
    // Deck marks the new viewport dirty and schedules `_customRender`; do not start a competing
172
    // React update here. Keeping redraw ownership in one callback handles both controlled and
173
    // uncontrolled view state without making WebGPU a separate synchronization path.
174
    return props.onViewStateChange?.(params);
6✔
175
  };
176

177
  const handleInteractionStateChange: DeckProps<ViewsT>['onInteractionStateChange'] = params => {
27✔
178
    if (inRender) {
3!
179
      // Callback may invoke a state update. Defer callback to after render() to avoid React error
180
      // In React StrictMode, render is executed twice and useEffect/useLayoutEffect is executed once
181
      // Store deferred parameters in ref so that we can access it in another render
UNCOV
182
      thisRef.interactionStateUpdateRequested = params;
×
183
    } else {
184
      thisRef.interactionStateUpdateRequested = null;
3✔
185
      props.onInteractionStateChange?.(params);
3✔
186
    }
187
  };
188

189
  // Update Deck's props. If Deck needs redraw, this will trigger a call to `_customRender` in
190
  // the next animation frame.
191
  // Needs to be called both from initial mount, and when new props are received
192
  const deckProps = useMemo(() => {
27✔
193
    const forwardProps: DeckProps<ViewsT> = {
24✔
194
      widgets: [],
195
      ...props,
196
      // Override user styling props. We will set the canvas style in render()
197
      style: null,
198
      width: '100%',
199
      height: '100%',
200
      parent: containerRef.current,
201
      canvas: canvasRef.current,
202
      layers: jsxProps.layers,
203
      onViewStateChange: handleViewStateChange,
204
      onInteractionStateChange: handleInteractionStateChange
205
    };
206

207
    if (jsxProps.views) {
24✔
208
      forwardProps.views = jsxProps.views;
3✔
209
    }
210

211
    // The defaultValue for _customRender is null, which would overwrite the definition
212
    // of _customRender. Remove to avoid frequently redeclaring the method here.
213
    delete forwardProps._customRender;
24✔
214

215
    if (thisRef.deck) {
24✔
216
      thisRef.deck.setProps(forwardProps);
13✔
217
      // Sync viewport tracking after the update. Without this, _customRender would see
218
      // stale lastRenderedViewports and trigger a redundant forceUpdate, causing
219
      // double renders on every viewport change when using externally managed view state.
220
      if (thisRef.deck.isInitialized) {
13!
221
        thisRef.lastRenderedViewports = thisRef.deck.getViewports();
13✔
222
      }
223
    }
224

225
    return forwardProps;
24✔
226
  }, [props]);
227

228
  useEffect(() => {
27✔
229
    const DeckClass = props.Deck || Deck;
11✔
230

231
    thisRef.deck = createDeckInstance(thisRef, DeckClass, {
11✔
232
      ...deckProps,
233
      parent: containerRef.current,
234
      canvas: canvasRef.current
235
    });
236

237
    return () => thisRef.deck?.finalize();
11✔
238
  }, []);
239

240
  useIsomorphicLayoutEffect(() => {
27✔
241
    // render has just been called. The children are positioned based on the current view state.
242
    // Redraw Deck canvas immediately, if necessary, using the current view state, so that it
243
    // matches the child components.
244
    redrawDeck(thisRef);
27✔
245

246
    // Execute deferred callbacks
247
    const {viewStateUpdateRequested, interactionStateUpdateRequested} = thisRef;
27✔
248
    if (viewStateUpdateRequested) {
27!
UNCOV
249
      handleViewStateChange(viewStateUpdateRequested);
×
250
    }
251
    if (interactionStateUpdateRequested) {
27!
UNCOV
252
      handleInteractionStateChange(interactionStateUpdateRequested);
×
253
    }
254
  });
255

256
  useImperativeHandle(ref, () => getRefHandles(thisRef), []);
27✔
257

258
  const currentViewports =
259
    thisRef.deck && thisRef.deck.isInitialized ? thisRef.deck.getViewports() : undefined;
27✔
260

261
  const {ContextProvider, width = '100%', height = '100%', id, style} = props;
27✔
262

263
  const {containerStyle, canvasStyle} = useMemo(
27✔
264
    () => extractStyles({width, height, style}),
27✔
265
    [width, height, style]
266
  );
267

268
  // Props changes may lead to 3 types of updates:
269
  // 1. Only the WebGL canvas - updated in Deck's render cycle (next animation frame)
270
  // 2. Only the DOM - updated in React's lifecycle (now)
271
  // 3. Both the WebGL canvas and the DOM - defer React rerender to next animation frame just
272
  //    before Deck redraw to ensure perfect synchronization & avoid excessive redraw
273
  //    This is because multiple changes may happen to Deck between two frames e.g. transition
274
  if (
27!
275
    (!thisRef.viewStateUpdateRequested && thisRef.lastRenderedViewports === currentViewports) || // case 2
57✔
276
    thisRef.version !== version // case 3 just before deck redraws
277
  ) {
278
    thisRef.lastRenderedViewports = currentViewports;
27✔
279
    thisRef.version = version;
27✔
280

281
    // Render the background elements (typically react-map-gl instances)
282
    // using the view descriptors
283
    const childrenUnderViews = positionChildrenUnderViews({
27✔
284
      children: jsxProps.children,
285
      deck: thisRef.deck,
286
      ContextProvider
287
    });
288

289
    const canvas = createElement('canvas', {
27✔
290
      key: 'canvas',
291
      id: id || 'deckgl-overlay',
54✔
292
      ref: canvasRef,
293
      style: canvasStyle
294
    });
295

296
    const eventRoot = createElement(
27✔
297
      'div',
298
      {
299
        key: 'deck-events-root',
300
        className: 'deck-events-root',
301
        style: {width, height}
302
      },
303
      [canvas, childrenUnderViews]
304
    );
305

306
    const widgetRoot = createElement('div', {
27✔
307
      key: 'deck-widgets-root',
308
      className: 'deck-widgets-root'
309
    });
310

311
    // Render deck.gl as the last child
312
    thisRef.control = createElement(
27✔
313
      'div',
314
      {id: `${id || 'deckgl'}-wrapper`, ref: containerRef, style: containerStyle},
54✔
315
      [eventRoot, widgetRoot]
316
    );
317
  }
318

319
  inRender = false;
27✔
320
  return thisRef.control;
27✔
321
}
322

323
const DeckGL = React.forwardRef(DeckGLWithRef) as <ViewsT extends ViewOrViews>(
4✔
324
  props: DeckGLProps<ViewsT>
325
) => React.ReactElement;
326

327
export default DeckGL;
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc