Skip to content
Draft
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 50 additions & 1 deletion modules/core/src/lib/deck.ts
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,15 @@ export default class Deck<ViewsT extends ViewOrViews = null> {
};
private _metricsCounter: number = 0;

/**
* Tracks which props were explicitly declared by the user.
* - Patch `setProps` accumulates keys across calls (once declared, always controlled).
* - Snapshot `_setPropsSnapshot` replaces this set on every render so that only the
* props the user actually declared in JSX are treated as controlled.
* Widgets call `isControlled(key)` to avoid overwriting props the app owns.
*/
private _controlledProps: Set<string> = new Set();

private _needsRedraw: false | string = 'Initial render';
private _pickRequest: {
mode: string;
Expand Down Expand Up @@ -447,8 +456,48 @@ export default class Deck<ViewsT extends ViewOrViews = null> {
}
}

/** Partially update props */
/**
* Declarative patch update: describes the desired state of a subset of props.
* Every supplied key is permanently marked as user-controlled so that widgets
* know not to overwrite it. Unmentioned props are left as-is.
*/
setProps(props: DeckProps<ViewsT>): void {
for (const key of Object.keys(props)) {
this._controlledProps.add(key);
}
this._applyProps(props);
}

/**
* Declarative full-snapshot update for framework wrappers (React, Vue, etc.).
* `explicitProps` contains only the keys the user actually declared — not
* framework defaults or wrapper-owned overrides. The controlled set is replaced
* rather than accumulated so it always reflects the current declaration, not the
* union of all past renders. `allProps` is the fully resolved snapshot used for
* rendering.
* @internal
*/
_setPropsSnapshot(explicitProps: Partial<DeckProps<ViewsT>>, allProps: DeckProps<ViewsT>): void {
this._controlledProps = new Set(Object.keys(explicitProps));
this._applyProps(allProps);
}

/**
* Returns true if the given prop key was explicitly supplied by the user via
* `setProps` or JSX. Widgets should avoid writing to controlled props:
*
* ```ts
* if (!deck.isControlled('viewState')) {
* deck.setProps({viewState: nextViewState});
* }
* ```
*/
isControlled(key: keyof DeckProps): boolean {
return this._controlledProps.has(key as string);
}

/** @internal Apply a props update without changing the controlled-props set. */
private _applyProps(props: DeckProps<ViewsT>): void {
this.stats.get('setProps Time').timeStart();

if ('onLayerHover' in props) {
Expand Down
20 changes: 19 additions & 1 deletion modules/react/src/deckgl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,17 @@ import type {DeckProps, View, Viewport} from '@deck.gl/core';

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

// Props the wrapper always overrides regardless of what the user writes in JSX.
// These must never be counted as user-controlled signals for widgets.
const WRAPPER_OWNED_KEYS = new Set<string>([
'style',
'width',
'height',
'parent',
'canvas',
'_customRender'
]);

/* eslint-disable max-statements, accessor-pairs */
type DeckInstanceRef<ViewsT extends ViewOrViews> = {
deck?: Deck<ViewsT>;
Expand Down Expand Up @@ -160,6 +171,8 @@ function DeckGLWithRef<ViewsT extends ViewOrViews = null>(
// the next animation frame.
// Needs to be called both from initial mount, and when new props are received
const deckProps = useMemo(() => {
// `forwardProps` is the fully resolved snapshot Deck uses for rendering. It includes
// wrapper-owned overrides (style, width, height, parent, canvas) and wrapped callbacks.
const forwardProps: DeckProps<ViewsT> = {
widgets: [],
...props,
Expand All @@ -180,7 +193,12 @@ function DeckGLWithRef<ViewsT extends ViewOrViews = null>(
delete forwardProps._customRender;

if (thisRef.deck) {
thisRef.deck.setProps(forwardProps);
// `explicitProps` contains only what the user actually wrote in JSX. Wrapper-owned
// keys are excluded so widgets can freely manage props the user left unset.
const explicitProps = Object.fromEntries(
Object.entries(props).filter(([k]) => !WRAPPER_OWNED_KEYS.has(k))
) as Partial<DeckProps<ViewsT>>;
thisRef.deck._setPropsSnapshot(explicitProps, forwardProps);
}

return forwardProps;
Expand Down
Loading