nexxtway/react-rainbow

View on GitHub
src/components/PresenceMap/component.js

Summary

Maintainability
C
1 day
Test Coverage
import React, { useRef, useState, useEffect } from 'react';
import PropTypes from 'prop-types';
import StyledContainer from './styled/container';
import StyledControls from './styled/controls';
import getMapContainerStyles from './getMapContainerStyles';

const MAX_ZOOM = 15;

export default function MapComponent(props) {
    const {
        showTraffic,
        showTransit,
        center,
        zoom,
        markers,
        style,
        className,
        children,
        type,
        isScriptLoaded,
        isScriptLoadSucceed,
        onMarkerClick,
    } = props;
    const container = useRef();
    const mapContainer = useRef();
    const [map, setMap] = useState();
    const [mapMarkers, updateMapMarkers] = useState([]);
    const [trafficLayer, setTrafficLayer] = useState(false);
    const [transitLayer, setTransitLayer] = useState(false);

    useEffect(() => {
        if (isScriptLoaded && isScriptLoadSucceed) {
            const mapOptions = {
                zoom: 2,
                center: { lat: 0, lng: 0 },
                disableDefaultUI: true,
            };
            const mapInstance = new window.google.maps.Map(mapContainer.current, mapOptions);
            const trafficInstance = new window.google.maps.TrafficLayer();
            const transitInstance = new window.google.maps.TransitLayer();
            setMap(mapInstance);
            setTrafficLayer(trafficInstance);
            setTransitLayer(transitInstance);
        }
    }, [isScriptLoaded, isScriptLoadSucceed]);

    const handleMarkerClick = (marker, index) => {
        onMarkerClick(marker, index);
    };

    useEffect(() => {
        if (map) {
            mapMarkers.forEach(marker => marker.setMap(null));
            if (Array.isArray(markers)) {
                const instances = markers.map((marker, index) => {
                    const instance = new window.google.maps.Marker({
                        position: marker.position,
                        icon: marker.icon,
                        map,
                        zIndex: Math.round(marker.position.lat * 100000),
                    });
                    instance.addListener('click', () => handleMarkerClick(marker, index));
                    return instance;
                });
                updateMapMarkers(instances);
            } else {
                updateMapMarkers([]);
            }
        }
        // eslint-disable-next-line react-hooks/exhaustive-deps
    }, [markers, map]);

    useEffect(() => {
        if (map) {
            trafficLayer.setMap(showTraffic ? map : null);
        }
    }, [showTraffic, map, trafficLayer]);

    useEffect(() => {
        if (map) {
            transitLayer.setMap(showTransit ? map : null);
        }
        // eslint-disable-next-line react-hooks/exhaustive-deps
    }, [showTransit, map]);

    useEffect(() => {
        if (map) {
            const hasNewMarkers = mapMarkers.length > 0;
            if (center === 'auto') {
                if (hasNewMarkers) {
                    const bounds = new window.google.maps.LatLngBounds();
                    mapMarkers.forEach(markerInstance =>
                        bounds.extend(markerInstance.getPosition()),
                    );
                    map.setCenter(bounds.getCenter());
                    if (mapMarkers.length > 1) {
                        map.fitBounds(bounds);
                    } else {
                        map.setZoom(MAX_ZOOM);
                    }
                }
            } else {
                map.setCenter(center);
                map.setZoom(MAX_ZOOM);
            }
        }
    }, [center, map, mapMarkers]);

    useEffect(() => {
        if (map) {
            map.setZoom(Math.min(zoom, MAX_ZOOM));
        }
    }, [zoom, map]);

    useEffect(() => {
        if (map) {
            map.setMapTypeId(type);
        }
    }, [type, map]);

    return (
        <StyledContainer style={style} className={className} ref={container}>
            <div ref={mapContainer} style={getMapContainerStyles(container.current)} />
            <StyledControls>{children}</StyledControls>
        </StyledContainer>
    );
}

MapComponent.propTypes = {
    isScriptLoaded: PropTypes.bool.isRequired,
    isScriptLoadSucceed: PropTypes.bool.isRequired,
    markers: PropTypes.arrayOf(
        PropTypes.shape({
            position: PropTypes.shape({
                lat: PropTypes.number,
                lng: PropTypes.number,
            }).isRequired,
            icon: PropTypes.shape({
                path: PropTypes.string.isRequired,
                fillColor: PropTypes.string,
                fillOpacity: PropTypes.number,
                scale: PropTypes.number,
                strokeColor: PropTypes.string,
                strokeOpacity: PropTypes.number,
                strokeWeight: PropTypes.number,
                rotation: PropTypes.number,
            }),
        }),
    ),
    className: PropTypes.string,
    style: PropTypes.object,
    zoom: PropTypes.number,
    center: PropTypes.oneOfType([
        PropTypes.shape({
            lat: PropTypes.number,
            lng: PropTypes.number,
        }),
        PropTypes.oneOf(['auto']),
    ]),
    showTraffic: PropTypes.bool,
    showTransit: PropTypes.bool,
    children: PropTypes.node,
    type: PropTypes.oneOf(['roadmap', 'satellite', 'hybrid', 'terrain']),
    onMarkerClick: PropTypes.func,
};

MapComponent.defaultProps = {
    markers: [],
    className: undefined,
    style: undefined,
    zoom: 2,
    center: 'auto',
    showTraffic: false,
    showTransit: false,
    children: null,
    type: 'roadmap',
    onMarkerClick: () => {},
};