Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

docs: introduce new "guides" section #118

Merged
merged 1 commit into from
Dec 1, 2023
Merged
Show file tree
Hide file tree
Changes from all 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
79 changes: 56 additions & 23 deletions docs/README.md
Original file line number Diff line number Diff line change
@@ -1,35 +1,68 @@
# Introduction

react-google-maps is a collection of [React](https://react.dev/) components and hooks for
the Google Maps Javascript API.
`@vis.gl/react-google-maps` is a collection of [React][react] components and
hooks for the Google Maps Javascript API. It was first provisioned by the
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

JavaScript

Google Maps Platform team, in order to provide solid integrations between
React and the Maps JavaScript API inspired by the integration between
`react-map-gl` and the map renderers based on `mapbox-gl-js`.

Want to contribute? See our [Developer Guide](./contributing.md)

## Design Philosophy

react-google-maps was first provisioned by the Google Maps Platform team, in order to provide solid integrations between
react and the Google Maps API inspired by the integration between react-map-gl and the map renderers based on
mapbox-gl-js.
The Google Maps JavaScript API follows an [imperative][wiki-imperative]
programming model. That is, you instruct the map to do something, and it
will execute the command at its own pace. The map also maintains a lot of
its own state-information and logic for updating it. For example, the virtual
camera and the various ways to control it are fully contained within the
Maps API, including all event-listeners that allow users to interact with
the map.

The Google Maps APIs are [imperative](https://en.wikipedia.org/wiki/Imperative_programming).
That is, you instruct the map to do something, and it will execute the command at its own pace.
The map also maintains a lot of its own state-information and logic for updating it, for example
the current viewport and the various ways to control it is fully contained within the maps API.
While this is typically what you'd expect from a map renderer, it also comes
with some problems in the context of a React application. We often need a
full synchronization of the map-state with the state of the application and
other components. Some examples:

While this may be fine in a lot of use-cases it does not work when many components need to synchronize
with each other. We sometimes render two maps side by side, and when the user interacts with one,
update both cameras. We draw React UI outside of the map container, that moves with the camera.
We also render WebGL graphic overlays on top of the map, most notably with [deck.gl](https://deck.gl).
- Two maps are rendered side by side, and user interactions in one of them
should update both maps.
- Other components and UI elements outside the map might need updating based on
the region currently visible, or they might trigger changes of the region
shown by the map.
- Integrations with overlays for data-visualization on top of the map, most
notably using [deck.gl][docs-deckgl].

In these use cases, in order for all components to synchronize correctly, they must have their shared
states managed by React. We might store the **source of truth** in a parent component state, or Redux
store, or hooks, and let it propagate down to the map as well as its peers.
In all of these cases, the application must have a shared state managed by
React to be able to synchronize the components correctly. This is referred
to as having a _single source of truth_. That could be stored in a parent
component state, a Redux store, or react-hooks, and let it propagate down
to the map and any other component.

Ultimately, in the spirit of the [reactive programming paradigm](https://en.wikipedia.org/wiki/Reactive_programming),
data always flows **down**. As long as the map manages its own state, as Google Maps is designed to do, we risk the
components going out of sync.
Ultimately, in the spirit of the [reactive programming paradigm]
[wiki-reactive] used in React, data from this single source of truth should
always flow down the component hierarchy. If components manage their own
state, as Google Maps is designed to do, we risk the components going out of
sync.

@vis.gl/react-google-maps can create a fully reactive wrapper for the Google Maps JavaScript API.
The [Map](./api-reference/components/map.md) component can be fully
[controlled](https://reactjs.org/docs/forms.html#controlled-components), that is, the map's camera would
never deviate from the props that have been assigned.
`@vis.gl/react-google-maps` provides a reactive wrapper for the Google Maps
JavaScript API while still leaving the map instance in charge of state
updates. In most cases, this is desirable, as controlling a map from mouse,
pointer and touch events is a complicated problem in itself which is solved
particularly well by the Maps JavaScript API.

This means that usually, the props specified for the camera are read as
initial values, and users will be able to interact with the map without
requiring the props to be updated. So by default, the map is allowed to
deviate from the specified values.

However, the [Map][docs-map] component can also be used as a fully [controlled]
[react-controlled] component. That is, the map's camera will never deviate
from the props that have been assigned. In this mode, the controls
provided by the map can still be used by listening for the appropriate
events and updating the state accordingly.

[wiki-imperative]: https://en.wikipedia.org/wiki/Imperative_programming
[wiki-reactive]: https://en.wikipedia.org/wiki/Reactive_programming
[docs-deckgl]: ./guides/deckgl-integration.md
[docs-map]: ./api-reference/components/map.md
[react]: https://react.dev/
[react-controlled]: https://react.dev/learn/sharing-state-between-components#controlled-and-uncontrolled-components
113 changes: 17 additions & 96 deletions docs/api-reference/components/map.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,6 @@
# `<Map>` Component

React component to display
a [Map](https://developers.google.com/maps/documentation/javascript/reference/map#Map) instance.

## Usage

## Single Map component
React component to display a [Map][gmp-map] instance.

```tsx
import React, {FunctionComponent} from 'react';
Expand All @@ -19,107 +14,31 @@ const App: FunctionComponent<Record<string, unknown>> = () => (
export default App;
```

## Multiple Map components

Apply an id to each `Map` component when using multiple `Map` components.

```tsx
import React, {FunctionComponent} from 'react';
import {APIProvider, Map} from '@vis.gl/react-google-maps';

const App: FunctionComponent<Record<string, unknown>> = () => (
<APIProvider apiKey={'Your API key here'}>
<Map id={'map-1'} zoom={10} center={{lat: 53.54992, lng: 10.00678}} />
<Map id={'map-2'} zoom={10} center={{lat: 53.54992, lng: 10.00678}} />
<Map id={'map-3'} zoom={10} center={{lat: 53.54992, lng: 10.00678}} />
<Map id={'map-4'} zoom={10} center={{lat: 53.54992, lng: 10.00678}} />
</APIProvider>
);
export default App;
```

## DeckGL Integration

### Using the `Map` as a child of `DeckGL`

To use deck.gl with the `Map` component wrap the `Map` component with
the [DeckGL React component](https://deck.gl/docs/get-started/using-with-react).

The following props need to be defined and passed to the DeckGL component:

- `intialViewState`
- `layers`
- `controller`
- `onViewStateChange`

Make sure that the controller is always set to true and an initial view state is defined. To ensure that deck.gl works
well with the Google Maps Platform map, set the `limitTiltRange` function, which can be imported from the Google Maps React
library, to `onViewStateChange`.

```tsx
import {limitTiltRange} from '@vis.gl/react-google-maps';

const App = () => (
<APIProvider apiKey={'Your API key here'}>
<DeckGL
initialViewState={INITIAL_VIEW_STATE}
layers={layers}
controller={true}
onViewStateChange={limitTiltRange}>
<Map {...GOOGLE_MAPS_MAP_OPTIONS} />
</DeckGL>
</APIProvider>
);
```

### Using GoogleMapsOverlay

Alternatively, you can also use the `GoogleMapsOverlay` provided by the `@deck.gl/google-maps` package to render deck.gl
content via Maps API `WebGlOverlayView`. An example for this can be found in [`./examples/deckgl-overlay`](https://github.com/visgl/react-google-maps/tree/1a0ac6e13d15ceda5212d310ffc2370ffdd90e65/examples/deckgl-overlay).
By default, the `Map` component is uncontrolled. That is, the props
for controlling the camera (`center`, `zoom`, `heading` and `tilt`) only
specify the initial value and the map will allow all user-interactions as is
default for the Maps JavaScript API.

For this you have to implement your own component and add it to the `Map` component.
A simplified version of this would be:

```javascript
import {useEffect, useMemo} from 'react';
import {useMap} from '@vis.gl/react-google-maps';
import {GoogleMapsOverlay} from '@deck.gl/google-maps';

export const DeckGlOverlay = ({layers}) => {
const deck = useMemo(() => new GoogleMapsOverlay({interleaved: true}), []);

const map = useMap();
useEffect(() => deck.setMap(map), [map]);
useEffect(() => deck.setProps({layers}), [layers]);

// no dom rendered by this component
return null;
};

const App = () => (
<APIProvider apiKey={API_KEY}>
<Map {...mapProps}>
<DeckGlOverlay layers={deckGlLayers} />
</Map>
</APIProvider>
);
```
Only when the values for these props are changed, the camera is updated by
the library to reflect those values.

## Props

The MapProps interface extends
the [google.maps.MapOptions interface](https://developers.google.com/maps/documentation/javascript/reference/map#MapOptions)
The MapProps interface extends the [google.maps.MapOptions interface]
(https://developers.google.com/maps/documentation/javascript/reference/map#MapOptions)
and includes all possible options available for a Google Maps Platform Map.

Additionally, there are other optional properties for the map component.

To add custom style, the `style` property can be set to add inline style to the default map style. Also, it is possible
to add a class to the map via the `className` property. The style passed with the `className` property will overwrite
the original default inline style of the map.
To add custom style, the `style` property can be set to add inline style to
the default map style. Also, it is possible to add a class to the map via
the `className` property. The style passed with the `className` property
will overwrite the original default inline style of the map.

The `initialBounds` property can receive bounds on the initial map load.
When multiple `Map` components are used, it is necessary to apply an id to
each Map instance. The id can be used to reference the

When multiple `Map` components are used, it is necessary to apply an id to each Map instance. The id can be used to reference the
Map instance when using the [`useMap` hook](../hooks/use-map.md).

```tsx
Expand All @@ -139,3 +58,5 @@ To see the Map on the screen, you must set `zoom` and `center` together, or `ini
options can also be set later via `map.setOptions(...)` when the Map instance is accessed via
the [useMap hook](../hooks/use-map.md). The props `viewport`, `viewState` and `initialViewState` are used
for an integration with the [DeckGL React Component](https://deck.gl/docs/get-started/using-with-react) and the Map.

[gmp-map]: https://developers.google.com/maps/documentation/javascript/reference/map#Map
66 changes: 66 additions & 0 deletions docs/guides/deckgl-integration.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# DeckGL Integration

## Using the `Map` as a child of `DeckGL`

To use deck.gl with the `Map` component wrap the `Map` component with
the [DeckGL React component](https://deck.gl/docs/get-started/using-with-react).

The following props need to be defined and passed to the DeckGL component:

- `intialViewState`
- `layers`
- `controller`
- `onViewStateChange`

Make sure that the controller is always set to true and an initial view state is defined. To ensure that deck.gl works
well with the Google Maps Platform map, set the `limitTiltRange` function, which can be imported from the Google Maps React
library, to `onViewStateChange`.

```tsx
import {limitTiltRange} from '@vis.gl/react-google-maps';

const App = () => (
<APIProvider apiKey={'Your API key here'}>
<DeckGL
initialViewState={INITIAL_VIEW_STATE}
layers={layers}
controller={true}
onViewStateChange={limitTiltRange}>
<Map {...GOOGLE_MAPS_MAP_OPTIONS} />
</DeckGL>
</APIProvider>
);
```

## Using GoogleMapsOverlay

Alternatively, you can also use the `GoogleMapsOverlay` provided by the `@deck.gl/google-maps` package to render deck.gl
content via Maps API `WebGlOverlayView`. An example for this can be found in [`./examples/deckgl-overlay`](https://github.com/visgl/react-google-maps/tree/1a0ac6e13d15ceda5212d310ffc2370ffdd90e65/examples/deckgl-overlay).

For this you have to implement your own component and add it to the `Map` component.
A simplified version of this would be:

```javascript
import {useEffect, useMemo} from 'react';
import {useMap} from '@vis.gl/react-google-maps';
import {GoogleMapsOverlay} from '@deck.gl/google-maps';

export const DeckGlOverlay = ({layers}) => {
const deck = useMemo(() => new GoogleMapsOverlay({interleaved: true}), []);

const map = useMap();
useEffect(() => deck.setMap(map), [map]);
useEffect(() => deck.setProps({layers}), [layers]);

// no dom rendered by this component
return null;
};

const App = () => (
<APIProvider apiKey={API_KEY}>
<Map {...mapProps}>
<DeckGlOverlay layers={deckGlLayers} />
</Map>
</APIProvider>
);
```
Original file line number Diff line number Diff line change
@@ -1,22 +1,22 @@
# Interacting with the Google Maps API

With the provided components, you are able to declaratively create a
[Google Map](./api-reference/components/map.md) or a map with
[markers](./api-reference/components/marker.md) for example.
[Google Map](../api-reference/components/map.md) or a map with
[markers](../api-reference/components/marker.md) for example.

Besides that, there are three main ways and concepts to interact
with the Google Maps API on lower level with this library.
You can use the provided hooks, the refs that are available for
some components or use the
[`useMapsLibrary`](./api-reference/hooks/use-maps-library.md) hook
[`useMapsLibrary`](../api-reference/hooks/use-maps-library.md) hook
to tap into other libraries and services of the Google Maps API or
craft your own custom hooks.

## Hooks

There are several hooks that provide additional functionality for the
map or maps you create.
The main one being the [`useMap`](./api-reference/hooks/use-map.md) hook.
The main one being the [`useMap`](../api-reference/hooks/use-map.md) hook.
This hooks give you access to the underlying `google.maps.Map` instance.
Every child component wrapped in the `<APIProvider>...</APIProvider>`
component has access to the map instance via this hook.
Expand Down Expand Up @@ -48,12 +48,12 @@ const App = () => (

Other hooks provide access to different Google Maps API services:

- [useDirectionsService](./api-reference/hooks/use-directions-service.md)
- [useDirectionsService](../api-reference/hooks/use-directions-service.md)
for the [Directions Service](https://developers.google.com/maps/documentation/javascript/directions)
- [useStreetViewPanorama](./api-reference/hooks/use-streetview-panorama.md) for the [Streetview Service](https://developers.google.com/maps/documentation/javascript/streetview)
- [useAutocomplete](./api-reference/hooks/use-autocomplete.md) for the [Places Widget](https://developers.google.com/maps/documentation/javascript/reference/places-widget)
- [useStreetViewPanorama](../api-reference/hooks/use-streetview-panorama.md) for the [Streetview Service](https://developers.google.com/maps/documentation/javascript/streetview)
- [useAutocomplete](../api-reference/hooks/use-autocomplete.md) for the [Places Widget](https://developers.google.com/maps/documentation/javascript/reference/places-widget)

The [useMapsLibrary](./api-reference/hooks/use-maps-library.md) hook can be
The [useMapsLibrary](../api-reference/hooks/use-maps-library.md) hook can be
utilized to load other parts of the Google Maps API that are not loaded by default.
For example, the Places Service or the Geocoding Service.
[Learn how to use this hook.](#other-google-maps-api-libraries-and-services)
Expand Down Expand Up @@ -104,7 +104,7 @@ export default App;
The Maps JavaScript API has a lot of [additional libraries](https://developers.google.com/maps/documentation/javascript/libraries)
for things like geocoding, routing, the Places API, Street View, and
a lot more. These libraries are not loaded by default, which is why this
module provides a hook [`useMapsLibrary()`](./api-reference/hooks/use-maps-library.md)
module provides a hook [`useMapsLibrary()`](../api-reference/hooks/use-maps-library.md)
to handle dynamic loading of those libraries.

For example, if you want to write a component that needs to use the
Expand Down
9 changes: 8 additions & 1 deletion docs/table-of-contents.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,19 @@
"items": [
"README",
"get-started",
"interacting-with-the-google-maps-api",
"whats-new",
"upgrade-guide",
"contributing"
]
},
{
"type": "category",
"label": "Guides",
"items": [
"guides/interacting-with-google-maps-api",
"guides/deckgl-integration"
]
},
{
"type": "category",
"label": "API Reference",
Expand Down
Loading