You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

react-google-maps数组为空时Marker仍显示的问题求助

问题:react-google-maps 空数组时标记未移除

我在GoogleMapComponent.js中使用react-google-maps组件,根据similarLocations属性渲染多个Marker。初始渲染时数组为空,地图无标记;父组件CreateLocation.js中用户输入触发请求,返回的相似位置数组传给子组件后,标记能正常显示。但当用户输入导致请求返回空数组时,尽管确认props传递的是空数组,之前的标记仍保留在地图上,请问如何在数组为空时移除标记?

父组件CreateLocation.js代码

import React, { useEffect, useState } from 'react'
import Input from '../../../components/input'
import { checkLocationMatches} from '../api'
import GoogleMapComponent from './GoogleMapComponent'

const CreateLocation = ({
    isEdit,
}) => {
    const [locationData, setLocationData] = useState({
        id: null,
        client: null,
        locationName: '',
        googleLocationDetails: '',
        nameAr: '',
        nameEn: '',
        street: '',
        description: '',
        category: null,
        clientCode: '',
        tags: [],
        lat: 0,
        lang: 0,
        gov: '',
        city: '',
        area: ''
    })
    const [similarLocations, setSimilarLocations] = useState([])

    const matchArabicName = async () => {
        try {
            const data = {
                lat: locationData.lat,
                lang: locationData.lang,
                clientId: locationData.client.value,
                matcher: 'name_ar',
                name: locationData.nameAr
            }
            const response = await checkLocationMatches(data)
            response.data ? setSimilarLocations(response.data) : setSimilarLocations([])
        } catch (err) {
            console.log(err.response, 'match err')
        }
    }

    useEffect(() => {
        if (locationData.lat !== 0 && locationData.nameAr.length > 0 && !isEdit) {
            matchArabicName()
        }
        // eslint-disable-next-line react-hooks/exhaustive-deps
    }, [locationData.lat, locationData.nameAr])

    return (
        <Container>
            <Body>
                <Form>
                    <Input
                        disableWithBorder={false}
                        label="Client Location Name (Arabic)"
                        name="nameAr"
                        placeholder="EX: Hyper one"
                        type="text"
                        value={locationData.nameAr}
                        onChange={e => handleLocationDetailsChange(e)}
                        required
                        isEditable
                    />
                </Form>

                <MapContainer>
                    <GoogleMapComponent
                        isMarkerShown
                        containerElement={
                            <div style={{ height: '100%', width: '100%' }} />
                        }
                        setMapLocationData={handleSetMapLocationData}
                        lat={locationData.lat}
                        lang={locationData.lang}
                        isOpen={true}
                        similarLocations={similarLocations}
                    />
                </MapContainer>
            </Body>
        </Container>
    )
}

export default CreateLocation

子组件GoogleMapComponent.js代码

/* eslint-disable eqeqeq */
import React, { useEffect, useState } from 'react';
import { compose, withProps } from 'recompose';
import { withScriptjs, withGoogleMap, GoogleMap, Marker, InfoWindow } from 'react-google-maps';
import MapMarker from '../../../assets/icons/map-marker.svg'
import SimilarMarkerIcon from '../../../assets/icons/similar-marker.svg'

const GoogleMapComponent = compose(
    withProps({
        googleMapURL:
            'https://maps.googleapis.com/maps/api/js?key=API_KEY&region=EG&language=ar&v=3.exp&libraries=geometry,drawing,places',
        loadingElement: <div style={{ height: `100%` }} />,
        mapElement: <div style={{ height: `100%` }} />,
    }),
    withScriptjs,
    withGoogleMap,
)(({
    isMarkerShown,
    multipleMarkers,
    similarLocations,
    readOnly
}) => {

    useEffect(() => {
        console.log(similarLocations, 'Similar locations')
        console.log(similarLocations.length, 'Similar locations length')
    }, [similarLocations])

    const onMarkerDragEnd = (coord) => {
        const { latLng } = coord;
        const lat = latLng.lat();
        const lng = latLng.lng();
        setCurrentPosition({ lat: lat, lang: lng });
        getReverseGeocodingData(lat, lng);
    };

    return (
        <GoogleMap
            defaultZoom={zoom || 12}
            zoom={zoom || 12}
            center={{ lat: currentPosition.lat, lng: currentPosition.lang }}
            onClick={(e) => onMarkerDragEnd(e)}>

            {isMarkerShown && !multipleMarkers && (
                <Marker
                    position={{ lat: currentPosition.lat, lng: currentPosition.lang }}
                    defaultDraggable={!readOnly}
                    onDragEnd={(e) => onMarkerDragEnd(e)}
                    options={{
                        icon: {
                            url: MapMarker,
                        },
                    }}
                />
            )}
            {
                similarLocations.length > 0 &&
                similarLocations.map(marker => (
                    <Marker
                        key={marker.location.id}
                        position={{ lat: +marker.location.latitude, lng: +marker.location.longitude }}
                        onClick={() => {
                            handleActiveMarker(marker.location.id)
                        }}
                        options={{
                            icon: {
                                url: SimilarMarkerIcon,
                            },
                        }}
                        visible={similarLocations.includes(marker)}
                    >
                        {activeMarker === marker.location.id ? (
                            <InfoWindow onCloseClick={() => setActiveMarker(null)}>
                                <div>{marker.location.name_ar}</div>
                            </InfoWindow>
                        ) : null}
                    </Marker>
                ))
            }
        </GoogleMap>
    );
});

export default GoogleMapComponent;

解决方案

1. 移除冗余的visible属性

Marker上的visible={similarLocations.includes(marker)}是问题核心:当similarLocations变为空数组时,这个判断无法正确标记标记为不可见,而且数组更新时对象引用变化会导致includes判断失效。直接删除该属性,让渲染逻辑完全由数组的map方法控制——空数组调用map不会生成任何Marker元素。

2. 优化渲染判断(可选)

原代码中similarLocations.length > 0 &&的判断可以保留,但即使去掉,空数组的map也不会渲染内容,不影响结果。保留的话可以减少不必要的map执行。

3. 检查内部状态的影响

子组件中使用的currentPosition、zoom、activeMarker等状态需确保定义正确,且handleActiveMarker、setActiveMarker能正确重置弹窗状态,避免InfoWindow残留。

修改后的子组件关键代码

// ... 其他代码不变
return (
    <GoogleMap
        defaultZoom={zoom || 12}
        zoom={zoom || 12}
        center={{ lat: currentPosition.lat, lng: currentPosition.lang }}
        onClick={(e) => onMarkerDragEnd(e)}>

        {/* 主标记渲染逻辑 */}
        {isMarkerShown && !multipleMarkers && (
            <Marker
                position={{ lat: currentPosition.lat, lng: currentPosition.lang }}
                defaultDraggable={!readOnly}
                onDragEnd={(e) => onMarkerDragEnd(e)}
                options={{
                    icon: {
                        url: MapMarker,
                    },
                }}
            />
        )}
        {/* 相似位置标记渲染逻辑 */}
        {similarLocations.length > 0 && similarLocations.map(marker => (
            <Marker
                key={marker.location.id}
                position={{ lat: +marker.location.latitude, lng: +marker.location.longitude }}
                onClick={() => {
                    handleActiveMarker(marker.location.id)
                }}
                options={{
                    icon: {
                        url: SimilarMarkerIcon,
                    },
                }}
            >
                {activeMarker === marker.location.id ? (
                    <InfoWindow onCloseClick={() => setActiveMarker(null)}>
                        <div>{marker.location.name_ar}</div>
                    </InfoWindow>
                ) : null}
            </Marker>
        ))}
    </GoogleMap>
);
// ... 其他代码不变

验证要点

  • 确认父组件中setSimilarLocations传入的是新数组(如response.data或[]),确保props引用变化触发子组件重新渲染。
  • 测试用户输入导致返回空数组的场景,观察地图上的相似位置标记是否完全移除。

内容的提问来源于stack exchange,提问作者Mohamed Karawia

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.25 08:58:11