` element:
8 |
9 | ```tsx
10 | import React, {useEffect} from 'react';
11 | import {
12 | useGoogleMap,
13 | useStreetViewPanorama
14 | } from '@ubilabs/google-maps-react-hooks';
15 |
16 | const MyComponent = () => {
17 | const [divContainer, setDivContainer] = useState
(null);
18 |
19 | const divRef = useCallback(
20 | (node: React.SetStateAction) => {
21 | node && setDivContainer(node);
22 | },
23 | []
24 | );
25 |
26 | const map = useGoogleMap();
27 |
28 | const position = /** google.maps.LatLng */;
29 | const pov = /** google.maps.StreetViewPov */;
30 |
31 | const panorama = useStreetViewPanorama({
32 | divElement: divContainer,
33 | position,
34 | pov
35 | });
36 |
37 | return ;
38 | };
39 | ```
40 |
41 | or be created on its own to be used by the map:
42 |
43 | ```tsx
44 | import React, {useEffect} from 'react';
45 | import {
46 | useGoogleMap,
47 | useStreetViewPanorama
48 | } from '@ubilabs/google-maps-react-hooks';
49 |
50 | const MyComponent = () => {
51 | const position = /** google.maps.LatLng */;
52 | const pov = /** google.maps.StreetViewPov */;
53 |
54 | useStreetViewPanorama({
55 | position,
56 | pov
57 | });
58 |
59 | return null;
60 | };
61 | ```
62 |
63 | **NOTE**:
64 | The map instance is only created and can be used with the `useStreetViewPanorama` hook when the `mapContainer` is passed to the `GoogleMapsProvider`.
65 |
66 | ## Return value
67 |
68 | Returns a [`StreetViewPanorama`](google.maps.StreetViewPanorama) instance to use directly.
69 |
70 | ```TypeScript
71 | google.maps.StreetViewPanorama
72 | ```
73 |
74 | ## Parameters
75 |
76 | ### StreetViewPanoramaProps
77 |
78 | Optional options that can be passed to display a street view location: [Street View Locations and Point-of-View (POV)](https://developers.google.com/maps/documentation/javascript/streetview#StreetViewLocation).
79 |
80 | ```TypeScript
81 | interface StreetViewPanoramaProps {
82 | divElement?: HTMLElement | null;
83 | position?: google.maps.LatLng | google.maps.LatLngLiteral;
84 | pov?: google.maps.StreetViewPov;
85 | zoom?: number;
86 | }
87 | ```
88 |
--------------------------------------------------------------------------------
/library/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@ubilabs/google-maps-react-hooks",
3 | "version": "2.0.2",
4 | "description": "React hooks and map context provider for Google Maps",
5 | "source": "src/index.ts",
6 | "main": "dist/index.umd.js",
7 | "module": "dist/index.modern.mjs",
8 | "types": "dist/index.d.ts",
9 | "homepage": "https://github.com/ubilabs/google-maps-react-hooks",
10 | "repository": {
11 | "type": "git",
12 | "url": "git://github.com/ubilabs/google-maps-react-hooks.git"
13 | },
14 | "scripts": {
15 | "build": "npm run barrel && npm run microbundle:build",
16 | "start": "run-p barrel:watch microbundle:dev",
17 | "microbundle:build": "rm -rf dist/* && microbundle -o dist/index.js -f modern,umd --external react=React --sourcemap false --jsx React.createElement --no-compress --tsconfig ./tsconfig.json",
18 | "microbundle:dev": "rm -rf dist/* && microbundle watch -o dist/index.js -f modern,umd --external react=React --sourcemap false --jsx React.createElement --no-compress --tsconfig ./tsconfig.json",
19 | "linter": "eslint './src/**/*.{ts,tsx}'",
20 | "typecheck": "tsc --project tsconfig.json --noEmit",
21 | "formatter": "prettier --check .",
22 | "test": "npm run linter && npm run typecheck && npm run formatter",
23 | "barrel": "eslint --fix src/index.ts",
24 | "barrel:watch": "nodemon --delay 1 -e ts,tsx --watch src/hooks -x 'npm run barrel --silent || exit 1'",
25 | "changelog": "conventional-changelog -p angular -i CHANGELOG.md -s -r 0",
26 | "preversion": "npm run test",
27 | "version": "npm run changelog && git checkout -b chore/release-${npm_package_version} && git add .",
28 | "postversion": "git push -u origin chore/release-${npm_package_version} && git push --tags --no-verify",
29 | "prepublishOnly": "npm run test && npm run build"
30 | },
31 | "keywords": [
32 | "React hooks",
33 | "Google Maps"
34 | ],
35 | "peerDependencies": {
36 | "react": ">=16.8.0"
37 | },
38 | "devDependencies": {
39 | "@types/google.maps": "^3.50.5",
40 | "@types/react": "^18.0.21",
41 | "@typescript-eslint/eslint-plugin": "^5.40.0",
42 | "@typescript-eslint/parser": "^5.40.0",
43 | "conventional-changelog-cli": "^2.1.1",
44 | "eslint": "^8.25.0",
45 | "eslint-config-prettier": "^8.3.0",
46 | "eslint-plugin-codegen": "^0.16.1",
47 | "eslint-plugin-import": "^2.23.4",
48 | "eslint-plugin-react": "^7.24.0",
49 | "eslint-plugin-react-hooks": "^4.2.0",
50 | "microbundle": "^0.15.1",
51 | "nodemon": "^2.0.20",
52 | "npm-run-all": "^4.1.5",
53 | "prettier": "^2.3.2",
54 | "typescript": "^4.3.5",
55 | "typescript-plugin-css-modules": "^3.4.0"
56 | },
57 | "bugs": {
58 | "url": "https://github.com/ubilabs/google-maps-react-hooks/issues"
59 | },
60 | "license": "MIT"
61 | }
62 |
--------------------------------------------------------------------------------
/library/src/google-maps-provider.tsx:
--------------------------------------------------------------------------------
1 | import React, {useState, useEffect, PropsWithChildren} from 'react';
2 |
3 | const GOOGLE_MAPS_API_URL = 'https://maps.googleapis.com/maps/api/js';
4 |
5 | // https://developers.google.com/maps/documentation/javascript/url-params
6 | export interface GoogleMapsAPIUrlParameters {
7 | googleMapsAPIKey: string;
8 | libraries?: string[];
9 | language?: string;
10 | region?: string;
11 | version?: string;
12 | authReferrerPolicy?: string;
13 | }
14 |
15 | export interface GoogleMapsConfiguration {
16 | mapContainer?: HTMLElement | null;
17 | mapOptions?: google.maps.MapOptions;
18 | }
19 |
20 | export interface GoogleMapsProviderProps
21 | extends GoogleMapsAPIUrlParameters,
22 | GoogleMapsConfiguration {
23 | onLoadScript?: () => void;
24 | onLoadMap?: (map: google.maps.Map) => void;
25 | }
26 |
27 | export interface GoogleMapsContextType {
28 | googleMapsAPIIsLoaded: boolean;
29 | map?: google.maps.Map;
30 | }
31 |
32 | // Declare global maps callback function
33 | declare global {
34 | interface Window {
35 | mapsCallback: () => void;
36 | }
37 | }
38 |
39 | /**
40 | * The Google Maps context
41 | */
42 | export const GoogleMapsContext = React.createContext({
43 | googleMapsAPIIsLoaded: false
44 | });
45 |
46 | /**
47 | * The global Google Maps provider
48 | */
49 | export const GoogleMapsProvider: React.FunctionComponent<
50 | PropsWithChildren
51 | > = props => {
52 | const {
53 | children,
54 | googleMapsAPIKey,
55 | mapContainer,
56 | mapOptions,
57 | libraries,
58 | language,
59 | region,
60 | version,
61 | authReferrerPolicy,
62 | onLoadScript,
63 | onLoadMap
64 | } = props;
65 |
66 | const [isLoadingAPI, setIsLoadingAPI] = useState(true);
67 | const [map, setMap] = useState();
68 |
69 | // Handle Google Maps API loading
70 | // eslint-disable-next-line complexity
71 | useEffect(() => {
72 | const apiLoadingFinished = () => {
73 | setIsLoadingAPI(false);
74 | onLoadScript && onLoadScript();
75 | };
76 |
77 | const defaultLanguage = navigator.language.slice(0, 2);
78 | const defaultRegion = navigator.language.slice(3, 5);
79 |
80 | /* eslint-disable camelcase */
81 | const params = new URLSearchParams({
82 | key: googleMapsAPIKey,
83 | language: language || defaultLanguage,
84 | region: region || defaultRegion,
85 | ...(libraries?.length && {libraries: libraries.join(',')}),
86 | ...(version && {v: version}),
87 | ...(authReferrerPolicy && {auth_referrer_policy: authReferrerPolicy})
88 | });
89 | /* eslint-enable camelcase */
90 |
91 | const existingScriptTag: HTMLScriptElement | null = document.querySelector(
92 | `script[src^="${GOOGLE_MAPS_API_URL}"]`
93 | );
94 |
95 | // Check if Google Maps API was loaded with the passed parameters
96 | if (existingScriptTag) {
97 | const loadedURL = new URL(existingScriptTag.src);
98 | const loadedParams = loadedURL.searchParams.toString();
99 | const passedParams = params.toString();
100 |
101 | if (loadedParams !== passedParams) {
102 | console.error(
103 | 'The Google Maps API Parameters passed to the `GoogleMapsProvider` components do not match. The Google Maps API can only be loaded once. Please make sure to pass the same API parameters to all of your `GoogleMapsProvider` components.',
104 | '\n\nExpected parameters:',
105 | Object.fromEntries(loadedURL.searchParams),
106 | '\n\nReceived parameters:',
107 | Object.fromEntries(params)
108 | );
109 | }
110 | }
111 |
112 | if (typeof google === 'object' && typeof google.maps === 'object') {
113 | // Google Maps API is already loaded
114 | apiLoadingFinished();
115 | } else if (existingScriptTag) {
116 | // Google Maps API is already loading
117 | setIsLoadingAPI(true);
118 |
119 | const onload = existingScriptTag.onload;
120 | existingScriptTag.onload = event => {
121 | onload?.call(existingScriptTag, event);
122 | apiLoadingFinished();
123 | };
124 | } else {
125 | // Load Google Maps API
126 | setIsLoadingAPI(true);
127 |
128 | // Add google maps callback
129 | window.mapsCallback = () => {
130 | apiLoadingFinished();
131 | };
132 |
133 | params.set('callback', 'mapsCallback');
134 |
135 | const scriptTag = document.createElement('script');
136 | scriptTag.type = 'text/javascript';
137 | scriptTag.src = `${GOOGLE_MAPS_API_URL}?${params.toString()}`;
138 | document.getElementsByTagName('head')[0].appendChild(scriptTag);
139 | }
140 |
141 | // Clean up Google Maps API
142 | return () => {
143 | // Remove all loaded Google Maps API scripts
144 | document
145 | .querySelectorAll('script[src^="https://maps.googleapis.com"]')
146 | .forEach(script => {
147 | script.remove();
148 | });
149 |
150 | // Remove google.maps global
151 | if (typeof google === 'object' && typeof google.maps === 'object') {
152 | // @ts-ignore: The operand of a 'delete' operator must be optional.
153 | delete google.maps;
154 | }
155 | };
156 | }, [
157 | googleMapsAPIKey,
158 | JSON.stringify(libraries),
159 | language,
160 | region,
161 | version,
162 | authReferrerPolicy
163 | ]);
164 |
165 | // Handle Google Maps map instance
166 | useEffect(() => {
167 | // Check for google.maps is needed because of Hot Module Replacement
168 | if (
169 | isLoadingAPI ||
170 | !mapContainer ||
171 | !(typeof google === 'object' && typeof google.maps === 'object')
172 | ) {
173 | return () => {};
174 | }
175 |
176 | const newMap = new google.maps.Map(mapContainer, mapOptions);
177 |
178 | google.maps.event.addListenerOnce(newMap, 'idle', () => {
179 | if (onLoadMap && newMap) {
180 | onLoadMap(newMap);
181 | }
182 | });
183 |
184 | setMap(newMap);
185 |
186 | // Remove all map related event listeners
187 | return () => {
188 | if (
189 | newMap &&
190 | typeof google === 'object' &&
191 | typeof google.maps === 'object'
192 | ) {
193 | google.maps.event.clearInstanceListeners(newMap);
194 | }
195 | };
196 | }, [isLoadingAPI, mapContainer]);
197 |
198 | return (
199 |
201 | {children}
202 |
203 | );
204 | };
205 |
--------------------------------------------------------------------------------
/library/src/hooks/autocomplete-service.ts:
--------------------------------------------------------------------------------
1 | import {useContext, useMemo} from 'react';
2 |
3 | import {GoogleMapsContext} from '../google-maps-provider';
4 |
5 | /**
6 | * Hook to get Google Maps Autocomplete Service instance
7 | */
8 | export const useAutocompleteService =
9 | (): google.maps.places.AutocompleteService | null => {
10 | const {googleMapsAPIIsLoaded} = useContext(GoogleMapsContext);
11 |
12 | // Creates an Autocomplete Service instance
13 | const autocompleteService =
14 | useMemo(() => {
15 | // Wait for Google Maps API to be loaded
16 | if (!googleMapsAPIIsLoaded) {
17 | return null;
18 | }
19 |
20 | if (!google.maps.places) {
21 | throw Error(
22 | "Places library missing. Add 'places' to the libraries array of GoogleMapsProvider."
23 | );
24 | }
25 |
26 | return new google.maps.places.AutocompleteService();
27 | }, [googleMapsAPIIsLoaded]);
28 |
29 | return autocompleteService;
30 | };
31 |
--------------------------------------------------------------------------------
/library/src/hooks/autocomplete.ts:
--------------------------------------------------------------------------------
1 | import {useContext, useState, useRef, useEffect} from 'react';
2 |
3 | import {GoogleMapsContext} from '../google-maps-provider';
4 |
5 | export interface AutocompleteProps {
6 | inputField: HTMLInputElement | null;
7 | options?: google.maps.places.AutocompleteOptions;
8 | onPlaceChanged: (place: google.maps.places.PlaceResult) => void;
9 | }
10 |
11 | /**
12 | * Hook to get a Google Maps Places Autocomplete instance
13 | * monitoring an input field
14 | */
15 | export const useAutocomplete = (
16 | props: AutocompleteProps
17 | ): google.maps.places.Autocomplete | null => {
18 | const {inputField, options, onPlaceChanged} = props;
19 | const placeChangedHandler = useRef(onPlaceChanged);
20 | const {googleMapsAPIIsLoaded} = useContext(GoogleMapsContext);
21 |
22 | const [autocomplete, setAutocomplete] =
23 | useState(null);
24 |
25 | // Initializes the Google Maps Places Autocomplete
26 | useEffect(() => {
27 | // Wait for the Google Maps API and input element to be initialized
28 | if (!googleMapsAPIIsLoaded || !inputField) {
29 | return (): void => {};
30 | }
31 |
32 | if (!google.maps.places) {
33 | throw Error(
34 | "Autocomplete library missing. Add 'places' to the libraries array of GoogleMapsProvider."
35 | );
36 | }
37 |
38 | // Create Autocomplete instance
39 | const autocompleteInstance = new google.maps.places.Autocomplete(
40 | inputField,
41 | options
42 | );
43 | setAutocomplete(autocompleteInstance);
44 |
45 | // Add places change listener to Autocomplete
46 | autocompleteInstance.addListener('place_changed', () => {
47 | const place = autocompleteInstance.getPlace();
48 | placeChangedHandler.current && placeChangedHandler.current(place);
49 | });
50 |
51 | // Clear listeners on unmount
52 | return (): void => {
53 | autocompleteInstance &&
54 | google.maps.event.clearInstanceListeners(autocompleteInstance);
55 | };
56 | }, [googleMapsAPIIsLoaded, inputField, options]);
57 |
58 | return autocomplete;
59 | };
60 |
--------------------------------------------------------------------------------
/library/src/hooks/directions-service.ts:
--------------------------------------------------------------------------------
1 | import {useContext, useMemo, useEffect, useCallback} from 'react';
2 |
3 | import {GoogleMapsContext} from '../google-maps-provider';
4 |
5 | export interface DirectionsServiceProps {
6 | renderOnMap?: boolean;
7 | renderOptions?: google.maps.DirectionsRendererOptions;
8 | }
9 |
10 | interface DirectionsServiceHookReturns {
11 | directionsService: google.maps.DirectionsService | null;
12 | directionsRenderer: google.maps.DirectionsRenderer | null;
13 | findRoute:
14 | | ((
15 | request: google.maps.DirectionsRequest
16 | ) => Promise)
17 | | null;
18 | findAndRenderRoute:
19 | | ((
20 | request: google.maps.DirectionsRequest
21 | ) => Promise)
22 | | null;
23 | renderRouteOfIndex: (index: number) => void;
24 | }
25 |
26 | /**
27 | * Hook to get Google Maps Places Directions Service instance
28 | */
29 | export const useDirectionsService = (
30 | props: DirectionsServiceProps = {}
31 | ): DirectionsServiceHookReturns => {
32 | const {renderOnMap, renderOptions} = props;
33 | const {googleMapsAPIIsLoaded, map} = useContext(GoogleMapsContext);
34 |
35 | // Creates a Directions Service instance
36 | const directionsService =
37 | useMemo(() => {
38 | // Wait for Google Maps API to be loaded
39 | if (!googleMapsAPIIsLoaded) {
40 | return null;
41 | }
42 |
43 | return new google.maps.DirectionsService();
44 | }, [googleMapsAPIIsLoaded]);
45 |
46 | // Creates a Directions Renderer instance
47 | const directionsRenderer =
48 | useMemo(() => {
49 | // Wait for map to be initialized
50 | if (!map || !renderOnMap) {
51 | return null;
52 | }
53 |
54 | const renderer = new google.maps.DirectionsRenderer(renderOptions);
55 | renderer.setMap(map);
56 |
57 | return renderer;
58 | }, [map, renderOnMap]);
59 |
60 | // Updates the directions renderer options
61 | useEffect(() => {
62 | if (!directionsRenderer) {
63 | return;
64 | }
65 |
66 | directionsRenderer.setOptions(renderOptions || {});
67 | }, [renderOptions]);
68 |
69 | // Custom Directions route request
70 | const findRoute = useCallback(
71 | (
72 | request: google.maps.DirectionsRequest
73 | ): Promise =>
74 | new Promise((resolve, reject) => {
75 | if (directionsService) {
76 | directionsService.route(
77 | request,
78 | (
79 | result: google.maps.DirectionsResult | null,
80 | status: google.maps.DirectionsStatus
81 | ): void => {
82 | if (status !== google.maps.DirectionsStatus.OK || !result) {
83 | reject(status);
84 | } else {
85 | resolve(result);
86 | }
87 | }
88 | );
89 | }
90 | }),
91 | [directionsService]
92 | );
93 |
94 | // Custom Directions route request followed by directions rendering
95 | const findAndRenderRoute = useCallback(
96 | (
97 | request: google.maps.DirectionsRequest
98 | ): Promise =>
99 | new Promise((resolve, reject) => {
100 | if (directionsService) {
101 | directionsService.route(
102 | request,
103 | (
104 | result: google.maps.DirectionsResult | null,
105 | status: google.maps.DirectionsStatus
106 | ): void => {
107 | if (status !== google.maps.DirectionsStatus.OK || !result) {
108 | reject(status);
109 | } else {
110 | if (directionsRenderer) {
111 | directionsRenderer.setDirections(result);
112 | }
113 |
114 | resolve(result);
115 | }
116 | }
117 | );
118 | }
119 | }),
120 | [directionsService, directionsRenderer]
121 | );
122 |
123 | // Renders directions route of given index
124 | const renderRouteOfIndex = (index: number) => {
125 | if (directionsRenderer) {
126 | directionsRenderer.setRouteIndex(index);
127 | }
128 | };
129 |
130 | return {
131 | directionsService,
132 | directionsRenderer,
133 | findRoute: directionsService && findRoute,
134 | findAndRenderRoute:
135 | directionsService && directionsRenderer && findAndRenderRoute,
136 | renderRouteOfIndex
137 | };
138 | };
139 |
--------------------------------------------------------------------------------
/library/src/hooks/distance-matrix-service.ts:
--------------------------------------------------------------------------------
1 | import {useContext, useMemo} from 'react';
2 |
3 | import {GoogleMapsContext} from '../google-maps-provider';
4 |
5 | /**
6 | * Hook to get Distance Matrix Service instance
7 | */
8 | export const useDistanceMatrixService =
9 | (): google.maps.DistanceMatrixService | null => {
10 | const {googleMapsAPIIsLoaded} = useContext(GoogleMapsContext);
11 |
12 | // Creates a Distance Matrix Service instance
13 | const distanceMatrixService =
14 | useMemo(() => {
15 | // Wait for Google Maps API to be loaded
16 | if (!googleMapsAPIIsLoaded) {
17 | return null;
18 | }
19 |
20 | if (!google.maps.DistanceMatrixService) {
21 | throw Error('Distance Matrix library missing.');
22 | }
23 |
24 | return new google.maps.DistanceMatrixService();
25 | }, [googleMapsAPIIsLoaded]);
26 |
27 | return distanceMatrixService;
28 | };
29 |
--------------------------------------------------------------------------------
/library/src/hooks/elevation-service.ts:
--------------------------------------------------------------------------------
1 | import {useContext, useMemo} from 'react';
2 |
3 | import {GoogleMapsContext} from '../google-maps-provider';
4 |
5 | /**
6 | * Hook to get Elevation Service instance
7 | */
8 | export const useElevationService = (): google.maps.ElevationService | null => {
9 | const {googleMapsAPIIsLoaded} = useContext(GoogleMapsContext);
10 |
11 | // Creates an Elevation Service instance
12 | const elevationService = useMemo(() => {
13 | // Wait for Google Maps API to be loaded
14 | if (!googleMapsAPIIsLoaded) {
15 | return null;
16 | }
17 |
18 | return new google.maps.ElevationService();
19 | }, [googleMapsAPIIsLoaded]);
20 |
21 | return elevationService;
22 | };
23 |
--------------------------------------------------------------------------------
/library/src/hooks/geocoding-service.ts:
--------------------------------------------------------------------------------
1 | import {useContext, useMemo} from 'react';
2 |
3 | import {GoogleMapsContext} from '../google-maps-provider';
4 |
5 | /**
6 | * Hook to get Google Maps Geocoder instance
7 | */
8 | export const useGeocodingService = (): google.maps.Geocoder | null => {
9 | const {googleMapsAPIIsLoaded} = useContext(GoogleMapsContext);
10 |
11 | // Creates a Geocoder instance
12 | const geocoder = useMemo(() => {
13 | // Wait for Google Maps API to be loaded
14 | if (!googleMapsAPIIsLoaded) {
15 | return null;
16 | }
17 |
18 | return new google.maps.Geocoder();
19 | }, [googleMapsAPIIsLoaded]);
20 |
21 | return geocoder;
22 | };
23 |
--------------------------------------------------------------------------------
/library/src/hooks/map-instance.ts:
--------------------------------------------------------------------------------
1 | import {useContext} from 'react';
2 |
3 | import {GoogleMapsContext} from '../google-maps-provider';
4 |
5 | /**
6 | * Hook to get global map instance
7 | */
8 | export const useGoogleMap = (): google.maps.Map | undefined =>
9 | useContext(GoogleMapsContext).map;
10 |
--------------------------------------------------------------------------------
/library/src/hooks/max-zoom-service.ts:
--------------------------------------------------------------------------------
1 | import {useContext, useMemo} from 'react';
2 |
3 | import {GoogleMapsContext} from '../google-maps-provider';
4 |
5 | /**
6 | * Hook to get Max Zoom Service instance
7 | */
8 | export const useMaxZoomService = (): google.maps.MaxZoomService | null => {
9 | const {googleMapsAPIIsLoaded} = useContext(GoogleMapsContext);
10 |
11 | // Creates a Max Zoom Service instance
12 | const maxZoomService = useMemo(() => {
13 | // Wait for Google Maps API to be loaded
14 | if (!googleMapsAPIIsLoaded) {
15 | return null;
16 | }
17 |
18 | return new google.maps.MaxZoomService();
19 | }, [googleMapsAPIIsLoaded]);
20 |
21 | return maxZoomService;
22 | };
23 |
--------------------------------------------------------------------------------
/library/src/hooks/places-service.ts:
--------------------------------------------------------------------------------
1 | import {useContext, useEffect, useState} from 'react';
2 |
3 | import {GoogleMapsContext} from '../google-maps-provider';
4 |
5 | export interface PlacesServiceProps {
6 | divElement?: HTMLDivElement | null;
7 | }
8 |
9 | /**
10 | * Hook to get Google Maps Places Service instance
11 | */
12 | export const usePlacesService = (
13 | props?: PlacesServiceProps
14 | ): google.maps.places.PlacesService | null => {
15 | const {map, googleMapsAPIIsLoaded} = useContext(GoogleMapsContext);
16 |
17 | const [placesService, setPlacesService] =
18 | useState(null);
19 |
20 | // Creates a Places Service instance
21 | useEffect(() => {
22 | if (!googleMapsAPIIsLoaded) {
23 | return;
24 | }
25 |
26 | if (!google.maps.places) {
27 | throw Error(
28 | "Places library missing. Add 'places' to the libraries array of GoogleMapsProvider."
29 | );
30 | }
31 |
32 | // Create places service which renders attributions in the map container
33 | if (props?.divElement === undefined) {
34 | // Wait for map to be initialized
35 | if (!map) {
36 | return;
37 | }
38 |
39 | const serviceMap = new google.maps.places.PlacesService(map);
40 | setPlacesService(serviceMap);
41 |
42 | return;
43 | }
44 |
45 | // Create places service which renders attributions in the passed div element
46 | // Wait for div element to be available
47 | if (!props?.divElement) {
48 | return;
49 | }
50 |
51 | const serviceElement = new google.maps.places.PlacesService(
52 | props?.divElement
53 | );
54 | setPlacesService(serviceElement);
55 | }, [googleMapsAPIIsLoaded, map, props?.divElement]);
56 |
57 | return placesService;
58 | };
59 |
--------------------------------------------------------------------------------
/library/src/hooks/street-view-panorama.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable complexity */
2 | import {useContext, useEffect, useState} from 'react';
3 |
4 | import {GoogleMapsContext} from '../google-maps-provider';
5 |
6 | export interface StreetViewPanoramaProps {
7 | divElement?: HTMLElement | null;
8 | position?: google.maps.LatLng | google.maps.LatLngLiteral;
9 | pov?: google.maps.StreetViewPov;
10 | zoom?: number;
11 | }
12 |
13 | /**
14 | * Hook to get Street View Panorama
15 | */
16 | export const useStreetViewPanorama = (
17 | props: StreetViewPanoramaProps
18 | ): google.maps.StreetViewPanorama | null => {
19 | const {divElement, position, pov, zoom} = props;
20 |
21 | const {googleMapsAPIIsLoaded, map} = useContext(GoogleMapsContext);
22 |
23 | const [streetViewPanorama, setStreetViewPanorama] =
24 | useState(null);
25 |
26 | // Creates a Street View instance
27 | useEffect(() => {
28 | // If no div element is passed, initialize a map with Street View Panorama
29 | if (!divElement) {
30 | // Wait for Google Maps map instance
31 | if (!map) {
32 | return (): void => {};
33 | }
34 |
35 | const newPanorama = map.getStreetView();
36 |
37 | if (pov) {
38 | newPanorama.setPov(pov);
39 | }
40 |
41 | if (position) {
42 | newPanorama.setPosition(position);
43 | }
44 |
45 | // eslint-disable-next-line no-eq-null
46 | if (zoom != null) {
47 | newPanorama.setZoom(zoom);
48 | }
49 |
50 | setStreetViewPanorama(newPanorama);
51 | } else {
52 | // Wait for Google Maps API
53 | if (!googleMapsAPIIsLoaded) {
54 | return (): void => {};
55 | }
56 |
57 | // If a div element is passed, initialize street view in the element
58 | const newPanorama = new google.maps.StreetViewPanorama(divElement, {
59 | position,
60 | pov,
61 | zoom
62 | });
63 |
64 | setStreetViewPanorama(newPanorama);
65 | }
66 |
67 | return (): void => {
68 | if (!divElement && map) {
69 | map.setStreetView(null);
70 | }
71 | };
72 | }, [map, divElement]);
73 |
74 | return streetViewPanorama;
75 | };
76 |
--------------------------------------------------------------------------------
/library/src/index.ts:
--------------------------------------------------------------------------------
1 | // codegen:start {preset: barrel, include: ./**/*, exclude: [./index.ts, ./types/*]}
2 | export * from './google-maps-provider';
3 | export * from './hooks/autocomplete-service';
4 | export * from './hooks/autocomplete';
5 | export * from './hooks/directions-service';
6 | export * from './hooks/distance-matrix-service';
7 | export * from './hooks/elevation-service';
8 | export * from './hooks/geocoding-service';
9 | export * from './hooks/map-instance';
10 | export * from './hooks/max-zoom-service';
11 | export * from './hooks/places-service';
12 | export * from './hooks/street-view-panorama';
13 | // codegen:end
14 |
--------------------------------------------------------------------------------
/library/src/types/index.d.ts:
--------------------------------------------------------------------------------
1 | declare module 'google.maps';
2 |
3 | /* eslint-disable init-declarations */
4 | declare module '*.module.css' {
5 | const classes: {[key: string]: string};
6 | export default classes;
7 | }
8 |
--------------------------------------------------------------------------------
/library/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../tsconfig.json",
3 | "compilerOptions": {
4 | "typeRoots": ["./src/types"]
5 | },
6 | "exclude": ["dist"]
7 | }
8 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@ubilabs/google-maps-react-hooks-library-and-examples",
3 | "description": "React hooks and map context provider for Google Maps",
4 | "homepage": "https://github.com/ubilabs/google-maps-react-hooks",
5 | "repository": {
6 | "type": "git",
7 | "url": "git://github.com/ubilabs/google-maps-react-hooks.git"
8 | },
9 | "scripts": {
10 | "test": "npm run test -ws --if-present",
11 | "start:library": "npm start -w library",
12 | "start:example": "./start-example.sh",
13 | "start:map-example": "cross-env EXAMPLE=map run-p start:library start:example",
14 | "start:map-with-markers-example": "cross-env EXAMPLE=map-with-markers run-p start:library start:example",
15 | "start:multiple-maps-example": "cross-env EXAMPLE=multiple-maps run-p start:library start:example",
16 | "start:geocoding-service-example": "cross-env EXAMPLE=geocoding-service run-p start:library start:example",
17 | "start:places-service-example": "cross-env EXAMPLE=places-service run-p start:library start:example",
18 | "start:places-service-with-element-example": "cross-env EXAMPLE=places-service-with-element run-p start:library start:example",
19 | "start:places-autocomplete-widget-example": "cross-env EXAMPLE=places-autocomplete-widget run-p start:library start:example",
20 | "start:directions-service-example": "cross-env EXAMPLE=directions-service run-p start:library start:example",
21 | "start:distance-matrix-service-example": "cross-env EXAMPLE=distance-matrix-service run-p start:library start:example",
22 | "start:elevation-service-example": "cross-env EXAMPLE=elevation-service run-p start:library start:example",
23 | "start:max-zoom-service-example": "cross-env EXAMPLE=max-zoom-service run-p start:library start:example",
24 | "start:places-autocomplete-service-example": "cross-env EXAMPLE=places-autocomplete-service run-p start:library start:example",
25 | "start:street-view-panorama-map-example": "cross-env EXAMPLE=street-view-panorama-map run-p start:library start:example",
26 | "start:street-view-panorama-element-example": "cross-env EXAMPLE=street-view-panorama-element run-p start:library start:example",
27 | "preversion": "echo \"To create a new library version run 'npm version -w library' in the repository root.\""
28 | },
29 | "keywords": [
30 | "React hooks",
31 | "Google Maps"
32 | ],
33 | "bugs": {
34 | "url": "https://github.com/ubilabs/google-maps-react-hooks/issues"
35 | },
36 | "license": "MIT",
37 | "workspaces": [
38 | "examples",
39 | "library"
40 | ],
41 | "devDependencies": {
42 | "cross-env": "^7.0.3",
43 | "npm-run-all": "^4.1.5"
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/start-example.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | if [ -z ${EXAMPLE+x} ]; then
3 | echo Check out the examples README for how to start a specific example.
4 | exit 1
5 | else
6 | npm run start:$EXAMPLE -w examples
7 | fi
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "strict": true,
4 | "sourceMap": true,
5 | "noImplicitAny": true,
6 | "module": "ES2015",
7 | "target": "es5",
8 | "lib": ["es2015", "dom"],
9 | "jsx": "react",
10 | "esModuleInterop": true,
11 | "types": ["node", "google.maps"],
12 | "moduleResolution": "node",
13 | "plugins": [{"name": "typescript-plugin-css-modules"}]
14 | },
15 | "exclude": ["node_modules"]
16 | }
17 |
--------------------------------------------------------------------------------