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

visgl / deck.gl / 23921223794

02 Apr 2026 08:46PM UTC coverage: 80.339% (-0.05%) from 80.387%
23921223794

Pull #10164

github

web-flow
Merge 6b0e48d35 into 2ff450d21
Pull Request #10164: feat(layers): TextLayer uses real text metrics

3115 of 3762 branches covered (82.8%)

Branch coverage included in aggregate %.

67 of 70 new or added lines in 4 files covered. (95.71%)

28 existing lines in 6 files now uncovered.

14235 of 17834 relevant lines covered (79.82%)

26684.89 hits per line

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

79.57
/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 {
2✔
58
    get deck() {
59
      return thisRef.deck;
3✔
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) {
7✔
72
    // Only redraw if we have received a dirty flag
73
    // @ts-expect-error accessing protected method
74
    thisRef.deck._drawLayers(thisRef.redrawReason);
3✔
75
    thisRef.redrawReason = null;
3✔
76
  }
77
}
78

79
function createDeckInstance<ViewsT extends ViewOrViews>(
80
  thisRef: DeckInstanceRef<ViewsT>,
81
  DeckClass: typeof Deck,
82
  props: DeckProps<ViewsT>
83
): Deck<ViewsT> {
84
  const deck = new DeckClass({
3✔
85
    ...props,
86
    // The Deck's animation loop is independent from React's render cycle, causing potential
87
    // synchronization issues. We provide this custom render function to make sure that React
88
    // and Deck update on the same schedule.
89
    // TODO(ibgreen) - Hack to enable WebGPU as it needs to render quickly to avoid CanvasContext texture from going stale
90
    _customRender:
91
      props.deviceProps?.adapters?.[0]?.type === 'webgpu'
92
        ? undefined
93
        : redrawReason => {
94
            // Save the dirty flag for later
95
            thisRef.redrawReason = redrawReason;
3✔
96

97
            // Viewport/view state is passed to child components as props.
98
            // If they have changed, we need to trigger a React rerender to update children props.
99
            const viewports = deck.getViewports();
3✔
100
            if (thisRef.lastRenderedViewports !== viewports) {
3✔
101
              // Viewports have changed, update children props first.
102
              // This will delay the Deck canvas redraw till after React update (in useLayoutEffect)
103
              // so that the canvas does not get rendered before the child components update.
104
              thisRef.forceUpdate();
1✔
105
            } else {
106
              redrawDeck(thisRef);
2✔
107
            }
108
          }
109
  });
110
  return deck;
3✔
111
}
112

113
function DeckGLWithRef<ViewsT extends ViewOrViews = null>(
114
  props: DeckGLProps<ViewsT>,
115
  ref: React.Ref<DeckGLRef<ViewsT>>
116
) {
117
  // A mechanism to force redraw
118
  const [version, setVersion] = useState(0);
6✔
119
  // A reference to persistent states
120
  const _thisRef = useRef<DeckInstanceRef<ViewsT>>({
6✔
121
    control: null,
122
    version,
123
    forceUpdate: () => setVersion(v => v + 1)
1✔
124
  });
125
  const thisRef = _thisRef.current;
6✔
126
  // DOM refs
127
  const containerRef = useRef(null);
6✔
128
  const canvasRef = useRef(null);
6✔
129

130
  // extract any deck.gl layers masquerading as react elements from props.children
131
  const jsxProps = useMemo(
6✔
132
    () => extractJSXLayers(props),
6✔
133
    [props.layers, props.views, props.children]
134
  );
135

136
  // Callbacks
137
  let inRender = true;
6✔
138

139
  const handleViewStateChange: DeckProps<ViewsT>['onViewStateChange'] = params => {
6✔
140
    if (inRender && props.viewState) {
×
141
      // Callback may invoke a state update. Defer callback to after render() to avoid React error
142
      // In React StrictMode, render is executed twice and useEffect/useLayoutEffect is executed once
143
      // Store deferred parameters in ref so that we can access it in another render
144
      thisRef.viewStateUpdateRequested = params;
×
145
      return null;
×
146
    }
147
    thisRef.viewStateUpdateRequested = null;
×
148
    return props.onViewStateChange?.(params);
×
149
  };
150

151
  const handleInteractionStateChange: DeckProps<ViewsT>['onInteractionStateChange'] = params => {
6✔
152
    if (inRender) {
×
153
      // Callback may invoke a state update. Defer callback to after render() to avoid React error
154
      // In React StrictMode, render is executed twice and useEffect/useLayoutEffect is executed once
155
      // Store deferred parameters in ref so that we can access it in another render
156
      thisRef.interactionStateUpdateRequested = params;
×
157
    } else {
158
      thisRef.interactionStateUpdateRequested = null;
×
159
      props.onInteractionStateChange?.(params);
×
160
    }
161
  };
162

163
  // Update Deck's props. If Deck needs redraw, this will trigger a call to `_customRender` in
164
  // the next animation frame.
165
  // Needs to be called both from initial mount, and when new props are received
166
  const deckProps = useMemo(() => {
6✔
167
    const forwardProps: DeckProps<ViewsT> = {
4✔
168
      widgets: [],
169
      ...props,
170
      // Override user styling props. We will set the canvas style in render()
171
      style: null,
172
      width: '100%',
173
      height: '100%',
174
      parent: containerRef.current,
175
      canvas: canvasRef.current,
176
      layers: jsxProps.layers,
177
      onViewStateChange: handleViewStateChange,
178
      onInteractionStateChange: handleInteractionStateChange
179
    };
180

181
    if (jsxProps.views) {
4✔
182
      forwardProps.views = jsxProps.views;
×
183
    }
184

185
    // The defaultValue for _customRender is null, which would overwrite the definition
186
    // of _customRender. Remove to avoid frequently redeclaring the method here.
187
    delete forwardProps._customRender;
4✔
188

189
    if (thisRef.deck) {
4✔
190
      thisRef.deck.setProps(forwardProps);
1✔
191
      // Sync viewport tracking after the update. Without this, _customRender would see
192
      // stale lastRenderedViewports and trigger a redundant forceUpdate, causing
193
      // double renders on every viewport change when using externally managed view state.
194
      if (thisRef.deck.isInitialized) {
1✔
195
        thisRef.lastRenderedViewports = thisRef.deck.getViewports();
1✔
196
      }
197
    }
198

199
    return forwardProps;
4✔
200
  }, [props]);
201

202
  useEffect(() => {
6✔
203
    const DeckClass = props.Deck || Deck;
3✔
204

205
    thisRef.deck = createDeckInstance(thisRef, DeckClass, {
3✔
206
      ...deckProps,
207
      parent: containerRef.current,
208
      canvas: canvasRef.current
209
    });
210

211
    return () => thisRef.deck?.finalize();
3✔
212
  }, []);
213

214
  useIsomorphicLayoutEffect(() => {
6✔
215
    // render has just been called. The children are positioned based on the current view state.
216
    // Redraw Deck canvas immediately, if necessary, using the current view state, so that it
217
    // matches the child components.
218
    redrawDeck(thisRef);
5✔
219

220
    // Execute deferred callbacks
221
    const {viewStateUpdateRequested, interactionStateUpdateRequested} = thisRef;
5✔
222
    if (viewStateUpdateRequested) {
5✔
UNCOV
223
      handleViewStateChange(viewStateUpdateRequested);
×
224
    }
225
    if (interactionStateUpdateRequested) {
5✔
UNCOV
226
      handleInteractionStateChange(interactionStateUpdateRequested);
×
227
    }
228

229
    // Force initial render if Deck is initialized
230
    if (thisRef.deck?.isInitialized) {
5✔
231
      thisRef.deck.redraw('Initial render');
2✔
232
    }
233
  });
234

235
  useImperativeHandle(ref, () => getRefHandles(thisRef), []);
6✔
236

237
  const currentViewports =
238
    thisRef.deck && thisRef.deck.isInitialized ? thisRef.deck.getViewports() : undefined;
6✔
239

240
  const {ContextProvider, width = '100%', height = '100%', id, style} = props;
6✔
241

242
  const {containerStyle, canvasStyle} = useMemo(
6✔
243
    () => extractStyles({width, height, style}),
6✔
244
    [width, height, style]
245
  );
246

247
  // Props changes may lead to 3 types of updates:
248
  // 1. Only the WebGL canvas - updated in Deck's render cycle (next animation frame)
249
  // 2. Only the DOM - updated in React's lifecycle (now)
250
  // 3. Both the WebGL canvas and the DOM - defer React rerender to next animation frame just
251
  //    before Deck redraw to ensure perfect synchronization & avoid excessive redraw
252
  //    This is because multiple changes may happen to Deck between two frames e.g. transition
253
  if (
6✔
254
    (!thisRef.viewStateUpdateRequested && thisRef.lastRenderedViewports === currentViewports) || // case 2
255
    thisRef.version !== version // case 3 just before deck redraws
256
  ) {
257
    thisRef.lastRenderedViewports = currentViewports;
5✔
258
    thisRef.version = version;
5✔
259

260
    // Render the background elements (typically react-map-gl instances)
261
    // using the view descriptors
262
    const childrenUnderViews = positionChildrenUnderViews({
6✔
263
      children: jsxProps.children,
264
      deck: thisRef.deck,
265
      ContextProvider
266
    });
267

268
    const canvas = createElement('canvas', {
6✔
269
      key: 'canvas',
270
      id: id || 'deckgl-overlay',
271
      ref: canvasRef,
272
      style: canvasStyle
273
    });
274

275
    const eventRoot = createElement(
6✔
276
      'div',
277
      {
278
        key: 'deck-events-root',
279
        className: 'deck-events-root',
280
        style: {width, height}
281
      },
282
      [canvas, childrenUnderViews]
283
    );
284

285
    const widgetRoot = createElement('div', {
6✔
286
      key: 'deck-widgets-root',
287
      className: 'deck-widgets-root'
288
    });
289

290
    // Render deck.gl as the last child
291
    thisRef.control = createElement(
5✔
292
      'div',
293
      {id: `${id || 'deckgl'}-wrapper`, ref: containerRef, style: containerStyle},
294
      [eventRoot, widgetRoot]
295
    );
296
  }
297

298
  inRender = false;
5✔
299
  return thisRef.control;
5✔
300
}
301

302
const DeckGL = React.forwardRef(DeckGLWithRef) as <ViewsT extends ViewOrViews>(
4✔
303
  props: DeckGLProps<ViewsT>
304
) => React.ReactElement;
305

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

© 2026 Coveralls, Inc