Skip to content

Commit

Permalink
fix: add explicit type for exported function components (#611)
Browse files Browse the repository at this point in the history
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.

closes #583
  • Loading branch information
usefulthink authored Nov 21, 2024
1 parent 0e673c2 commit a5b0359
Show file tree
Hide file tree
Showing 14 changed files with 107 additions and 99 deletions.
6 changes: 3 additions & 3 deletions src/components/__tests__/map-control.test.tsx
Original file line number Diff line number Diff line change
@@ -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';

Expand All @@ -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<PropsWithChildren>;

beforeEach(() => {
initialize();

wrapper = ({children}: {children: React.ReactNode}) => (
wrapper = ({children}) => (
<APIProvider apiKey={'apikey'}>
<Map zoom={10} center={{lat: 0, lng: 0}}>
{children}
Expand Down
6 changes: 3 additions & 3 deletions src/components/__tests__/map.test.tsx
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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<PropsWithChildren>;
let mockContextValue: jest.MockedObject<APIProviderContextValue>;
let createMapSpy: jest.Mock<
void,
Expand All @@ -29,7 +29,7 @@ beforeEach(() => {
clearMapInstances: jest.fn()
};

wrapper = ({children}: {children: React.ReactNode}) => (
wrapper = ({children}) => (
<APIProviderContext.Provider value={mockContextValue}>
{children}
</APIProviderContext.Provider>
Expand Down
6 changes: 3 additions & 3 deletions src/components/__tests__/marker.test.tsx
Original file line number Diff line number Diff line change
@@ -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';

Expand All @@ -9,15 +9,15 @@ import MockedFunction = jest.MockedFunction;

jest.mock('../../libraries/google-maps-api-loader');

let wrapper: ({children}: {children: React.ReactNode}) => JSX.Element | null;
let wrapper: FunctionComponent<PropsWithChildren>;
let createMarkerSpy: jest.Mock;

beforeEach(() => {
// initialize the Maps JavaScript API mocks
initialize();

// Create wrapper component
wrapper = ({children}: {children: React.ReactNode}) => (
wrapper = ({children}) => (
<APIProvider apiKey={'apikey'}>
<GoogleMap zoom={10} center={{lat: 0, lng: 0}}>
{children}
Expand Down
6 changes: 3 additions & 3 deletions src/components/__tests__/pin.test.tsx
Original file line number Diff line number Diff line change
@@ -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';

Expand All @@ -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<PropsWithChildren>;

let createMarkerSpy: jest.Mock<
void,
Expand All @@ -26,7 +26,7 @@ beforeEach(() => {
initialize();

// Create wrapper component
wrapper = ({children}: {children: React.ReactNode}) => (
wrapper = ({children}) => (
<APIProvider apiKey={'apikey'} libraries={['places']}>
<GoogleMap zoom={10} center={{lat: 0, lng: 0}}>
{children}
Expand Down
8 changes: 4 additions & 4 deletions src/components/advanced-marker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import React, {
Children,
CSSProperties,
forwardRef,
ForwardRefExoticComponent,
useCallback,
useEffect,
useImperativeHandle,
Expand Down Expand Up @@ -274,8 +275,8 @@ function useAdvancedMarker(props: AdvancedMarkerProps) {
return [marker, contentContainer] as const;
}

export const AdvancedMarker = forwardRef(
(props: AdvancedMarkerProps, ref: Ref<AdvancedMarkerRef>) => {
export const AdvancedMarker: ForwardRefExoticComponent<AdvancedMarkerProps> =
forwardRef((props, ref: Ref<AdvancedMarkerRef>) => {
const {children, style, className, anchorPoint} = props;
const [marker, contentContainer] = useAdvancedMarker(props);

Expand All @@ -299,8 +300,7 @@ export const AdvancedMarker = forwardRef(
)}
</AdvancedMarkerContext.Provider>
);
}
);
});

export function useAdvancedMarkerRef() {
const [marker, setMarker] =
Expand Down
10 changes: 4 additions & 6 deletions src/components/api-provider.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React, {
FunctionComponent,
PropsWithChildren,
ReactElement,
useCallback,
useEffect,
useMemo,
Expand Down Expand Up @@ -33,7 +33,7 @@ const DEFAULT_SOLUTION_CHANNEL = 'GMP_visgl_rgmlibrary_v1_default';
export const APIProviderContext =
React.createContext<APIProviderContextValue | null>(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:
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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<APIProviderProps>
): ReactElement | null => {
export const APIProvider: FunctionComponent<APIProviderProps> = props => {
const {children, ...loaderProps} = props;
const {mapInstances, addMapInstance, removeMapInstance, clearMapInstances} =
useMapInstances();
Expand Down
5 changes: 4 additions & 1 deletion src/components/info-window.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/* eslint-disable complexity */
import React, {
CSSProperties,
FunctionComponent,
PropsWithChildren,
ReactNode,
useEffect,
Expand Down Expand Up @@ -34,7 +35,9 @@ export type InfoWindowProps = Omit<
/**
* Component to render an Info Window with the Maps JavaScript API
*/
export const InfoWindow = (props: PropsWithChildren<InfoWindowProps>) => {
export const InfoWindow: FunctionComponent<
PropsWithChildren<InfoWindowProps>
> = props => {
const {
// content options
children,
Expand Down
7 changes: 5 additions & 2 deletions src/components/map-control.tsx
Original file line number Diff line number Diff line change
@@ -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';

Expand Down Expand Up @@ -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<MapControlProps> = ({
children,
position
}) => {
const controlContainer = useMemo(() => document.createElement('div'), []);
const map = useMap();

Expand Down
4 changes: 2 additions & 2 deletions src/components/map/auth-failure-message.tsx
Original file line number Diff line number Diff line change
@@ -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,
Expand Down
114 changes: 59 additions & 55 deletions src/components/map/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/* eslint-disable complexity */
import React, {
CSSProperties,
FunctionComponent,
PropsWithChildren,
useContext,
useEffect,
Expand Down Expand Up @@ -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<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 const Map = (props: PropsWithChildren<MapProps>) => {
export const Map: FunctionComponent<MapProps> = (props: MapProps) => {
const {children, id, className, style} = props;
const context = useContext(APIProviderContext);
const loadingStatus = useApiLoadingStatus();
Expand Down Expand Up @@ -240,4 +240,8 @@ export const Map = (props: PropsWithChildren<MapProps>) => {
</div>
);
};
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;
13 changes: 8 additions & 5 deletions src/components/marker.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/* eslint-disable complexity */
import React, {
forwardRef,
ForwardRefExoticComponent,
useCallback,
useEffect,
useImperativeHandle,
Expand Down Expand Up @@ -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<MarkerProps> = 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<google.maps.Marker | null>(null);
Expand Down
5 changes: 3 additions & 2 deletions src/components/pin.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {
Children,
FunctionComponent,
PropsWithChildren,
useContext,
useEffect,
Expand All @@ -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<google.maps.marker.PinElementOptions>;

/**
* Component to configure the appearance of an AdvancedMarker
*/
export const Pin = (props: PropsWithChildren<PinProps>) => {
export const Pin: FunctionComponent<PinProps> = props => {
const advancedMarker = useContext(AdvancedMarkerContext)?.marker;
const glyphContainer = useMemo(() => document.createElement('div'), []);

Expand Down
Loading

0 comments on commit a5b0359

Please sign in to comment.