From 52e4b09e8d13edf478cda32ce2ad953ff343c5be Mon Sep 17 00:00:00 2001 From: Martin Schuhfuss Date: Thu, 21 Nov 2024 16:38:47 +0100 Subject: [PATCH] fix: add explicit type for exported function components Adding an explicit type to the components we export avoids typescript from inferring the type as JSX.Element, which is incompatible with some react-versions. --- src/components/__tests__/map-control.test.tsx | 6 +- src/components/__tests__/map.test.tsx | 6 +- src/components/__tests__/marker.test.tsx | 6 +- src/components/__tests__/pin.test.tsx | 6 +- src/components/advanced-marker.tsx | 8 +- src/components/api-provider.tsx | 10 +- src/components/info-window.tsx | 5 +- src/components/map-control.tsx | 7 +- src/components/map/auth-failure-message.tsx | 4 +- src/components/map/index.tsx | 114 +++++++++--------- src/components/marker.tsx | 13 +- src/components/pin.tsx | 5 +- src/hooks/__tests__/api-loading.test.tsx | 6 +- src/hooks/__tests__/use-map.test.tsx | 10 +- 14 files changed, 107 insertions(+), 99 deletions(-) diff --git a/src/components/__tests__/map-control.test.tsx b/src/components/__tests__/map-control.test.tsx index 74067af1..b0568f0f 100644 --- a/src/components/__tests__/map-control.test.tsx +++ b/src/components/__tests__/map-control.test.tsx @@ -1,6 +1,6 @@ import '@testing-library/jest-dom'; -import React, {ReactElement} from 'react'; +import React, {FunctionComponent, PropsWithChildren} from 'react'; import {initialize} from '@googlemaps/jest-mocks'; import {cleanup, render} from '@testing-library/react'; @@ -11,12 +11,12 @@ import {waitForMockInstance} from './__utils__/wait-for-mock-instance'; jest.mock('../../libraries/google-maps-api-loader'); -let wrapper: ({children}: {children: React.ReactNode}) => ReactElement | null; +let wrapper: FunctionComponent; beforeEach(() => { initialize(); - wrapper = ({children}: {children: React.ReactNode}) => ( + wrapper = ({children}) => ( {children} diff --git a/src/components/__tests__/map.test.tsx b/src/components/__tests__/map.test.tsx index 7a043e1c..7f0464ce 100644 --- a/src/components/__tests__/map.test.tsx +++ b/src/components/__tests__/map.test.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, {FunctionComponent, PropsWithChildren} from 'react'; import {render, screen, waitFor} from '@testing-library/react'; import {initialize, mockInstances} from '@googlemaps/jest-mocks'; import '@testing-library/jest-dom'; @@ -9,7 +9,7 @@ import {APILoadingStatus} from '../../libraries/api-loading-status'; jest.mock('../../libraries/google-maps-api-loader'); -let wrapper: ({children}: {children: React.ReactNode}) => JSX.Element | null; +let wrapper: FunctionComponent; let mockContextValue: jest.MockedObject; let createMapSpy: jest.Mock< void, @@ -29,7 +29,7 @@ beforeEach(() => { clearMapInstances: jest.fn() }; - wrapper = ({children}: {children: React.ReactNode}) => ( + wrapper = ({children}) => ( {children} diff --git a/src/components/__tests__/marker.test.tsx b/src/components/__tests__/marker.test.tsx index ec2939d1..fabff8d5 100644 --- a/src/components/__tests__/marker.test.tsx +++ b/src/components/__tests__/marker.test.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, {FunctionComponent, PropsWithChildren} from 'react'; import {initialize, mockInstances} from '@googlemaps/jest-mocks'; import {cleanup, render, waitFor} from '@testing-library/react'; @@ -9,7 +9,7 @@ import MockedFunction = jest.MockedFunction; jest.mock('../../libraries/google-maps-api-loader'); -let wrapper: ({children}: {children: React.ReactNode}) => JSX.Element | null; +let wrapper: FunctionComponent; let createMarkerSpy: jest.Mock; beforeEach(() => { @@ -17,7 +17,7 @@ beforeEach(() => { initialize(); // Create wrapper component - wrapper = ({children}: {children: React.ReactNode}) => ( + wrapper = ({children}) => ( {children} diff --git a/src/components/__tests__/pin.test.tsx b/src/components/__tests__/pin.test.tsx index 216d59b0..34f73209 100644 --- a/src/components/__tests__/pin.test.tsx +++ b/src/components/__tests__/pin.test.tsx @@ -1,4 +1,4 @@ -import React, {JSX} from 'react'; +import React, {FunctionComponent, PropsWithChildren} from 'react'; import {initialize, mockInstances} from '@googlemaps/jest-mocks'; import {cleanup, render} from '@testing-library/react'; @@ -11,7 +11,7 @@ import {waitForSpy} from './__utils__/wait-for-spy'; jest.mock('../../libraries/google-maps-api-loader'); -let wrapper: ({children}: {children: React.ReactNode}) => JSX.Element | null; +let wrapper: FunctionComponent; let createMarkerSpy: jest.Mock< void, @@ -26,7 +26,7 @@ beforeEach(() => { initialize(); // Create wrapper component - wrapper = ({children}: {children: React.ReactNode}) => ( + wrapper = ({children}) => ( {children} diff --git a/src/components/advanced-marker.tsx b/src/components/advanced-marker.tsx index c6f3b690..3db6f596 100644 --- a/src/components/advanced-marker.tsx +++ b/src/components/advanced-marker.tsx @@ -3,6 +3,7 @@ import React, { Children, CSSProperties, forwardRef, + ForwardRefExoticComponent, useCallback, useEffect, useImperativeHandle, @@ -254,8 +255,8 @@ function useAdvancedMarker(props: AdvancedMarkerProps) { return [marker, contentContainer] as const; } -export const AdvancedMarker = forwardRef( - (props: AdvancedMarkerProps, ref: Ref) => { +export const AdvancedMarker: ForwardRefExoticComponent = + forwardRef((props, ref: Ref) => { const {children, style, className, anchorPoint} = props; const [marker, contentContainer] = useAdvancedMarker(props); @@ -279,8 +280,7 @@ export const AdvancedMarker = forwardRef( )} ); - } -); + }); export function useAdvancedMarkerRef() { const [marker, setMarker] = diff --git a/src/components/api-provider.tsx b/src/components/api-provider.tsx index a1fa73f4..90a28f7f 100644 --- a/src/components/api-provider.tsx +++ b/src/components/api-provider.tsx @@ -1,6 +1,6 @@ import React, { + FunctionComponent, PropsWithChildren, - ReactElement, useCallback, useEffect, useMemo, @@ -33,7 +33,7 @@ const DEFAULT_SOLUTION_CHANNEL = 'GMP_visgl_rgmlibrary_v1_default'; export const APIProviderContext = React.createContext(null); -export type APIProviderProps = { +export type APIProviderProps = PropsWithChildren<{ /** * apiKey must be provided to load the Google Maps JavaScript API. To create an API key, see: https://developers.google.com/maps/documentation/javascript/get-api-key * Part of: @@ -89,7 +89,7 @@ export type APIProviderProps = { * A function that will be called if there was an error when loading the Google Maps JavaScript API. */ onError?: (error: unknown) => void; -}; +}>; /** * local hook to set up the map-instance management context. @@ -225,9 +225,7 @@ function useGoogleMapsApiLoader(props: APIProviderProps) { /** * Component to wrap the components from this library and load the Google Maps JavaScript API */ -export const APIProvider = ( - props: PropsWithChildren -): ReactElement | null => { +export const APIProvider: FunctionComponent = props => { const {children, ...loaderProps} = props; const {mapInstances, addMapInstance, removeMapInstance, clearMapInstances} = useMapInstances(); diff --git a/src/components/info-window.tsx b/src/components/info-window.tsx index 8cdfaad2..b2b34f80 100644 --- a/src/components/info-window.tsx +++ b/src/components/info-window.tsx @@ -1,6 +1,7 @@ /* eslint-disable complexity */ import React, { CSSProperties, + FunctionComponent, PropsWithChildren, ReactNode, useEffect, @@ -34,7 +35,9 @@ export type InfoWindowProps = Omit< /** * Component to render an Info Window with the Maps JavaScript API */ -export const InfoWindow = (props: PropsWithChildren) => { +export const InfoWindow: FunctionComponent< + PropsWithChildren +> = props => { const { // content options children, diff --git a/src/components/map-control.tsx b/src/components/map-control.tsx index 3a20f76c..f875142f 100644 --- a/src/components/map-control.tsx +++ b/src/components/map-control.tsx @@ -1,4 +1,4 @@ -import {useEffect, useMemo} from 'react'; +import {FunctionComponent, useEffect, useMemo} from 'react'; import {createPortal} from 'react-dom'; import {useMap} from '../hooks/use-map'; @@ -46,7 +46,10 @@ export const ControlPosition = { export type ControlPosition = (typeof ControlPosition)[keyof typeof ControlPosition]; -export const MapControl = ({children, position}: MapControlProps) => { +export const MapControl: FunctionComponent = ({ + children, + position +}) => { const controlContainer = useMemo(() => document.createElement('div'), []); const map = useMap(); diff --git a/src/components/map/auth-failure-message.tsx b/src/components/map/auth-failure-message.tsx index c629f846..bedb46e4 100644 --- a/src/components/map/auth-failure-message.tsx +++ b/src/components/map/auth-failure-message.tsx @@ -1,6 +1,6 @@ -import React, {CSSProperties} from 'react'; +import React, {CSSProperties, FunctionComponent} from 'react'; -export const AuthFailureMessage = () => { +export const AuthFailureMessage: FunctionComponent = () => { const style: CSSProperties = { position: 'absolute', top: 0, diff --git a/src/components/map/index.tsx b/src/components/map/index.tsx index ebb00d9e..5be71823 100644 --- a/src/components/map/index.tsx +++ b/src/components/map/index.tsx @@ -1,6 +1,7 @@ /* eslint-disable complexity */ import React, { CSSProperties, + FunctionComponent, PropsWithChildren, useContext, useEffect, @@ -63,61 +64,60 @@ export type RenderingType = (typeof RenderingType)[keyof typeof RenderingType]; /** * Props for the Map Component */ -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. - */ - controlled?: boolean; - - /** - * Enable caching of map-instances created by this component. - */ - reuseMaps?: boolean; - - defaultCenter?: google.maps.LatLngLiteral; - defaultZoom?: number; - defaultHeading?: number; - defaultTilt?: number; - /** - * Alternative way to specify the default camera props as a geographic region that should be fully visible - */ - defaultBounds?: google.maps.LatLngBoundsLiteral & { - padding?: number | google.maps.Padding; - }; - }; +export type MapProps = PropsWithChildren< + Omit & + 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. + */ + controlled?: boolean; + + /** + * Enable caching of map-instances created by this component. + */ + reuseMaps?: boolean; + + defaultCenter?: google.maps.LatLngLiteral; + defaultZoom?: number; + defaultHeading?: number; + defaultTilt?: number; + /** + * Alternative way to specify the default camera props as a geographic region that should be fully visible + */ + defaultBounds?: google.maps.LatLngBoundsLiteral & { + padding?: number | google.maps.Padding; + }; + } +>; -export const Map = (props: PropsWithChildren) => { +export const Map: FunctionComponent = (props: MapProps) => { const {children, id, className, style} = props; const context = useContext(APIProviderContext); const loadingStatus = useApiLoadingStatus(); @@ -240,4 +240,8 @@ export const Map = (props: PropsWithChildren) => { ); }; -Map.deckGLViewProps = true; + +// The deckGLViewProps flag here indicates to deck.gl that the Map component is +// able to handle viewProps from deck.gl when deck.gl is used to control the map. +// eslint-disable-next-line @typescript-eslint/no-explicit-any +(Map as any).deckGLViewProps = true; diff --git a/src/components/marker.tsx b/src/components/marker.tsx index 0b7983e2..a0bbdb6c 100644 --- a/src/components/marker.tsx +++ b/src/components/marker.tsx @@ -1,6 +1,7 @@ /* eslint-disable complexity */ import React, { forwardRef, + ForwardRefExoticComponent, useCallback, useEffect, useImperativeHandle, @@ -118,13 +119,15 @@ function useMarker(props: MarkerProps) { /** * Component to render a marker on a map */ -export const Marker = forwardRef((props: MarkerProps, ref: MarkerRef) => { - const marker = useMarker(props); +export const Marker: ForwardRefExoticComponent = forwardRef( + (props: MarkerProps, ref: MarkerRef) => { + const marker = useMarker(props); - useImperativeHandle(ref, () => marker, [marker]); + useImperativeHandle(ref, () => marker, [marker]); - return <>; -}); + return <>; + } +); export function useMarkerRef() { const [marker, setMarker] = useState(null); diff --git a/src/components/pin.tsx b/src/components/pin.tsx index f5549c1f..daa96d13 100644 --- a/src/components/pin.tsx +++ b/src/components/pin.tsx @@ -1,5 +1,6 @@ import { Children, + FunctionComponent, PropsWithChildren, useContext, useEffect, @@ -12,12 +13,12 @@ import {logErrorOnce} from '../libraries/errors'; /** * Props for the Pin component */ -export type PinProps = google.maps.marker.PinElementOptions; +export type PinProps = PropsWithChildren; /** * Component to configure the appearance of an AdvancedMarker */ -export const Pin = (props: PropsWithChildren) => { +export const Pin: FunctionComponent = props => { const advancedMarker = useContext(AdvancedMarkerContext)?.marker; const glyphContainer = useMemo(() => document.createElement('div'), []); diff --git a/src/hooks/__tests__/api-loading.test.tsx b/src/hooks/__tests__/api-loading.test.tsx index 02462175..396452c4 100644 --- a/src/hooks/__tests__/api-loading.test.tsx +++ b/src/hooks/__tests__/api-loading.test.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, {FunctionComponent, PropsWithChildren} from 'react'; import {initialize} from '@googlemaps/jest-mocks'; import {renderHook} from '@testing-library/react'; @@ -11,7 +11,7 @@ import {useApiLoadingStatus} from '../use-api-loading-status'; import {useApiIsLoaded} from '../use-api-is-loaded'; import {APILoadingStatus} from '../../libraries/api-loading-status'; -let wrapper: ({children}: {children: React.ReactNode}) => JSX.Element | null; +let wrapper: FunctionComponent; let mockContextValue: jest.MockedObject; beforeEach(() => { initialize(); @@ -26,7 +26,7 @@ beforeEach(() => { clearMapInstances: jest.fn() }; - wrapper = ({children}: {children: React.ReactNode}) => ( + wrapper = ({children}) => ( {children} diff --git a/src/hooks/__tests__/use-map.test.tsx b/src/hooks/__tests__/use-map.test.tsx index 6cc7a768..a1bff61f 100644 --- a/src/hooks/__tests__/use-map.test.tsx +++ b/src/hooks/__tests__/use-map.test.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, {FunctionComponent, PropsWithChildren} from 'react'; import '@testing-library/jest-dom'; import {renderHook} from '@testing-library/react'; import {initialize, mockInstances} from '@googlemaps/jest-mocks'; @@ -11,11 +11,7 @@ import { import {Map as GoogleMap} from '../../components/map'; import {APILoadingStatus} from '../../libraries/api-loading-status'; -let MockApiContextProvider: ({ - children -}: { - children: React.ReactNode; -}) => JSX.Element | null; +let MockApiContextProvider: FunctionComponent; let mockContextValue: jest.MockedObject; let createMapSpy: jest.Mock< void, @@ -36,7 +32,7 @@ beforeEach(() => { clearMapInstances: jest.fn() }; - MockApiContextProvider = ({children}: {children: React.ReactNode}) => ( + MockApiContextProvider = ({children}) => ( {children}