Skip to content

Commit

Permalink
fix(map): add support for new and missing mapOptions (#501)
Browse files Browse the repository at this point in the history
- add support for colorScheme
- add support for renderingType
- add missing options to mapOptionKeys
- update docs with new props
- update tests for props that can't be changed after map creation
  • Loading branch information
usefulthink authored Sep 2, 2024
1 parent f275735 commit f22af50
Show file tree
Hide file tree
Showing 6 changed files with 161 additions and 22 deletions.
64 changes: 49 additions & 15 deletions docs/api-reference/components/map.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,20 +83,23 @@ If your application renders the map on a subpage or otherwise mounts and
unmounts the `Map` component a lot, this can cause new map instances to be
created with every mount of the component. Since the pricing of the Google
Maps JavaScript API is based on map-views (effectively calls to the
`google.maps.Map` constructor), this can quickly become a problem.
`google.maps.Map` constructor), this can quickly cause a problem.

The `Map` component can be configured to re-use already created maps with
the `reuseMaps` prop. When enabled, all `Map` components created with the same
`mapId` will reuse previously created instances instead of creating new ones.
the [`reuseMaps`](#reusemaps-boolean) prop.
When enabled, all `Map` components created with the same [`mapId`](#mapid-string),
[`colorScheme`](#colorscheme-googlemapscolorschemegmp-color-scheme-type) and
[`renderingType`](#renderingtype-googlemapsrenderingtypegmp-rendering-type) will reuse
previously created instances instead of creating new ones.

:::warning

In the 1.0.0 version, support for map-caching is still a bit experimental, and
In the current version, support for map-caching is still a bit experimental, and
there are some known issues when maps are being reused with different sets
of options applied. In most simpler situations, like when showing the very
same component multiple times, it should work fine.

If you experience any problems using this feature, please file a
If you experience any problems using this feature, please file a
[bug-report][rgm-new-issue] or [start a discussion][rgm-discussions] on GitHub.

:::
Expand Down Expand Up @@ -131,6 +134,19 @@ to be provided in some way for the map to render. This can be done

### General Props

:::info

The Maps JavaScript API doesn't allow changing the Map ID, the color scheme
or the rendering-type of a map after it has been created. This isn't the
case for this component. However, the internal `google.maps.Map` instance
has to be recreated when the `mapId`, `colorScheme` or `renderingType` props
are changed, **which will cause additional cost**.

See the [`reuseMaps`](#reusemaps-boolean) prop if your application has to
dynamically switch between multiple Map IDs, color schemes or rendering types.

:::

#### `id`: string

A string that identifies the map component. This is required when multiple
Expand All @@ -139,20 +155,33 @@ maps are present in the same APIProvider context to be able to access them using

#### `mapId`: string

The [Map ID][gmp-mapid] of the map.
The [Map ID][gmp-mapid] of the map. This is required if you want to make use
of the [Cloud-based maps styling][gmp-map-styling].

:::info
#### `colorScheme`: [google.maps.ColorScheme][gmp-color-scheme-type]

The [color-scheme][gmp-color-scheme] to be used by the map. Can be
`'LIGHT'`, `'DARK'`, `'FOLLOW_SYSTEM'` or one of the
`ColorScheme` constants
(`import {ColorScheme} from '@vis.gl/react-google-maps';`).

The Maps JavaScript API doesn't allow changing the Map ID of a map after it
has been created. This isn't the case for this component. However, the
internal `google.maps.Map` instance has to be recreated when the `mapId` prop
changes, which might cause additional cost.
:::note

See the [reuseMaps](#reusemaps-boolean) parameter if your application has to
repeatedly switch between multiple Map IDs.
Custom styles that use Map IDs only apply to the light color scheme for roadmap map types.

:::

#### `renderingType`: [google.maps.RenderingType][gmp-rendering-type]

The desired rendering type the renderer should use. Can be `'RASTER'` or
`'VECTOR'` or one of the `RenderingType` constants
(`import {RenderingType} from '@vis.gl/react-google-maps';`).

If not set, the cloud configuration for the map ID will determine the
rendering type (if available). Please note that vector maps may not be
available for all devices and browsers, and the map will fall back to a
raster map as needed.

#### `style`: React.CSSProperties

Additional style rules to apply to the map dom-element. By default, this will
Expand All @@ -166,8 +195,9 @@ style-prop is no longer applied.

#### `reuseMaps`: boolean

Enable map-instance caching for this component. When caching is enabled,
this component will reuse map instances created with the same `mapId`.
Enable map-instance caching for this component. When caching is enabled,
this component will reuse map instances created with the same `mapId`,
`colorScheme` and `renderingType`.

See also the section [Map Instance Caching](#map-instance-caching) above.

Expand Down Expand Up @@ -333,7 +363,11 @@ to get access to the `google.maps.Map` object rendered in the `<Map>` component.
[gmp-pad]: https://developers.google.com/maps/documentation/javascript/reference/coordinates#Padding
[gmp-ll]: https://developers.google.com/maps/documentation/javascript/reference/coordinates#LatLngLiteral
[gmp-coordinates]: https://developers.google.com/maps/documentation/javascript/coordinates
[gmp-color-scheme]: https://developers.google.com/maps/documentation/javascript/mapcolorscheme
[gmp-color-scheme-type]: https://developers.google.com/maps/documentation/javascript/reference/map#ColorScheme
[gmp-mapid]: https://developers.google.com/maps/documentation/get-map-id
[gmp-map-styling]: https://developers.google.com/maps/documentation/javascript/cloud-customization
[gmp-rendering-type]: https://developers.google.com/maps/documentation/javascript/reference/map#RenderingType
[api-provider]: ./api-provider.md
[get-max-tilt]: https://github.com/visgl/react-google-maps/blob/4319bd3b68c40b9aa9b0ce7f377b52d20e824849/src/libraries/limit-tilt-range.ts#L4-L19
[map-source]: https://github.com/visgl/react-google-maps/tree/main/src/components/map
Expand Down
49 changes: 49 additions & 0 deletions src/components/__tests__/map.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,55 @@ describe('creating and updating map instance', () => {
const [, options] = createMapSpy.mock.lastCall!;
expect(options).toMatchObject({mapId: 'othermapid'});
});

test('recreates the map when the colorScheme is changed', () => {
createMapSpy.mockReset();
rerender(
<GoogleMap
id={'mymap'}
mapId={'mymapid'}
center={center}
zoom={14}
colorScheme={'DARK'}
/>
);

expect(createMapSpy).toHaveBeenCalled();

const [, options] = createMapSpy.mock.lastCall!;
expect(options).toMatchObject({colorScheme: 'DARK'});
});

test('recreates the map when the renderingType is changed', () => {
createMapSpy.mockReset();
rerender(
<GoogleMap
id={'mymap'}
mapId={'mymapid'}
center={center}
zoom={14}
renderingType={'VECTOR'}
/>
);

expect(createMapSpy).toHaveBeenCalled();

const [, options] = createMapSpy.mock.lastCall!;
expect(options).toMatchObject({renderingType: 'VECTOR'});
});
});

describe('map instance caching', () => {
test.todo(
"map isn't recreated when unmounting and remounting with the same props"
);
test.todo(
'map is recreated when unmounting and remounting with changed mapId'
);
test.todo(
"map isn't recreated when unmounting and remounting with regular changed options"
);
test.todo('removed options are handled correctly');
});

describe('camera configuration', () => {
Expand Down
1 change: 0 additions & 1 deletion src/components/info-window.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
/* eslint-disable complexity */
import React, {
ComponentType,
CSSProperties,
PropsWithChildren,
ReactNode,
Expand Down
35 changes: 34 additions & 1 deletion src/components/map/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,25 +43,58 @@ export type MapCameraProps = {
tilt?: number;
};

// ColorScheme and RenderingType are redefined here to make them usable before the
// maps API has been fully loaded.

export const ColorScheme = {
DARK: 'DARK',
LIGHT: 'LIGHT',
FOLLOW_SYSTEM: 'FOLLOW_SYSTEM'
};
export type ColorScheme = (typeof ColorScheme)[keyof typeof ColorScheme];

export const RenderingType = {
VECTOR: 'VECTOR',
RASTER: 'RASTER',
UNINITIALIZED: 'UNINITIALIZED'
};
export type RenderingType = (typeof RenderingType)[keyof typeof RenderingType];

/**
* Props for the Map Component
*/
export type MapProps = google.maps.MapOptions &
export type MapProps = Omit<
google.maps.MapOptions,
'renderingType' | 'colorScheme'
> &
MapEventProps &
DeckGlCompatProps & {
/**
* An id for the map, this is required when multiple maps are present
* in the same APIProvider context.
*/
id?: string;

/**
* Additional style rules to apply to the map dom-element.
*/
style?: CSSProperties;

/**
* Additional css class-name to apply to the element containing the map.
*/
className?: string;

/**
* The color-scheme to use for the map.
*/
colorScheme?: ColorScheme;

/**
* The rendering-type to be used.
*/
renderingType?: RenderingType;

/**
* Indicates that the map will be controlled externally. Disables all controls provided by the map itself.
*/
Expand Down
30 changes: 26 additions & 4 deletions src/components/map/use-map-instance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,8 @@ export function useMapInstance(
defaultHeading,
defaultTilt,
reuseMaps,
renderingType,
colorScheme,

...mapOptions
} = props;
Expand Down Expand Up @@ -114,8 +116,10 @@ export function useMapInstance(

const {addMapInstance, removeMapInstance} = context;

const mapId = props.mapId;
const cacheKey = mapId || 'default';
// note: colorScheme (upcoming feature) isn't yet in the typings, remove once that is fixed:
const {mapId} = props;
const cacheKey = `${mapId || 'default'}:${renderingType || 'default'}:${colorScheme || 'LIGHT'}`;

let mapDiv: HTMLElement;
let map: google.maps.Map;

Expand All @@ -133,7 +137,15 @@ export function useMapInstance(
mapDiv = document.createElement('div');
mapDiv.style.height = '100%';
container.appendChild(mapDiv);
map = new google.maps.Map(mapDiv, mapOptions);
map = new google.maps.Map(mapDiv, {
...mapOptions,
renderingType: renderingType as google.maps.RenderingType,
// The colorScheme option and google.maps.ColorScheme type haven't been added
// to the @types/google.maps package yet, so this will cause a TS error:
// @ts-expect-error TS2353: Object literal may only specify known properties,

Check failure on line 145 in src/components/map/use-map-instance.ts

View workflow job for this annotation

GitHub Actions / test

Unused '@ts-expect-error' directive.
// and colorScheme does not exist in type MapOptions
colorScheme: colorScheme as google.maps.ColorScheme
});
}

setMap(map);
Expand Down Expand Up @@ -186,7 +198,17 @@ export function useMapInstance(
// changes should be ignored
// - mapOptions has special hooks that take care of updating the options
// eslint-disable-next-line react-hooks/exhaustive-deps
[container, apiIsLoaded, id, props.mapId]
[
container,
apiIsLoaded,
id,

// these props can't be changed after initialization and require a new
// instance to be created
props.mapId,
props.renderingType,
props.colorScheme
]
);

return [map, containerRef, cameraStateRef] as const;
Expand Down
4 changes: 3 additions & 1 deletion src/components/map/use-map-options.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {MapProps} from '../map';
import {useDeepCompareEffect} from '../../libraries/use-deep-compare-effect';

const mapOptionKeys = new Set([
const mapOptionKeys: Set<keyof google.maps.MapOptions> = new Set([
'backgroundColor',
'clickableIcons',
'controlSize',
Expand All @@ -13,6 +13,7 @@ const mapOptionKeys = new Set([
'fullscreenControl',
'fullscreenControlOptions',
'gestureHandling',
'headingInteractionEnabled',
'isFractionalZoomEnabled',
'keyboardShortcuts',
'mapTypeControl',
Expand All @@ -33,6 +34,7 @@ const mapOptionKeys = new Set([
'streetViewControl',
'streetViewControlOptions',
'styles',
'tiltInteractionEnabled',
'zoomControl',
'zoomControlOptions'
]);
Expand Down

0 comments on commit f22af50

Please sign in to comment.