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

React+TypeScript中点击Leaflet Marker调用setState的问题解决

问题

我通过Array.map渲染设备列表,使用Context API维护[selectedEquipmentId, setSelectedEquipmentId]状态。希望点击<Marker/>组件时调用setSelectedEquipmentId(equipmentId),但常规的onClick操作在Leaflet中无效,尝试多种方法均未解决。

相关代码

Map组件代码

function Map() {
  const equipmentsPositions = useContext(EquipmentsPositionContext);
  const setSelectedEquipmentId = useContext(EquipmentIdContext);

  const center: LatLngExpression = [-19.151801, -46.007759];


  function getEquipmentById(equipmentId: string) {
    const filteredEquipment = equipmentsStateHistory.filter(
      (equipment) => equipment.equipmentId === equipmentId
    );

    return filteredEquipment[0];
  }

  return (
    <Box h="100vh" w="80%">
      <MapContainer
        center={center}
        zoom={10}
        scrollWheelZoom={true}
        style={{ height: "100%", width: "100%" }}
      >
        <TileLayer
          attribution='&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
          url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
        />

        {equipmentsPositions?.map((equipmentPosition) => {
          const equipment = getEquipmentById(equipmentPosition.equipmentId);

          return (
            <Marker
              onClick={() => {setSelectedEquipmentId(equipment.equipmentId)}}
              key={equipment.equipmentId}
              position={[
                equipmentPosition.positions[equipmentsPositions.length - 1].lat,
                equipmentPosition.positions[equipmentsPositions.length - 1].lon,
              ]}
            >
              <Popup>
                <Text as="span" color={equipment.color}>
                  {equipment.name}
                </Text>
              </Popup>
            </Marker>
          );
        })}

      </MapContainer>
    </Box>
  );
}

export default Map;

Context代码

interface ISelectedEquipmentIdContext {
  selectedEquipmentId: string | ReactNode;
  setSelectedEquipmentId: React.Dispatch<React.SetStateAction<string>>
}

export const EquipmentIdContext = createContext<ISelectedEquipmentIdContext | null>(
  null
);

export function EquipmentIdProvider({ children }: IProps) {
  const [selectedEquipmentId, setSelectedEquipmentId] = useState<string>("");

  return (
    <EquipmentIdContext.Provider value={{ selectedEquipmentId, setSelectedEquipmentId }}>
      {children}
    </EquipmentIdContext.Provider>
  );
}

解决方案

1. 修正Context的获取逻辑

你当前直接从EquipmentIdContext获取setSelectedEquipmentId是错误的,因为Context提供的是包含selectedEquipmentId和setSelectedEquipmentId的对象,而非单独的函数。需要解构获取,同时添加非空判断避免报错:

const { setSelectedEquipmentId } = useContext(EquipmentIdContext) || {};

2. 改用react-leaflet标准事件绑定方式

Leaflet的Marker组件推荐使用eventHandlers属性绑定事件,而非直接用onClick,这能避免框架事件绑定的兼容问题:

<Marker
  key={equipment.equipmentId}
  position={/* 坐标 */}
  eventHandlers={{
    click: () => {
      // 确保equipment存在再执行状态更新
      equipment?.equipmentId && setSelectedEquipmentId?.(equipment.equipmentId);
    }
  }}
>

3. 增加空值校验

在渲染Marker前,先校验equipment是否存在,避免后续属性访问报错:

{equipmentsPositions?.map((equipmentPosition) => {
  const equipment = getEquipmentById(equipmentPosition.equipmentId);
  if (!equipment) return null; // 空值直接跳过渲染
  // ... 渲染Marker
})}

修正后的完整Map组件

function Map() {
  const equipmentsPositions = useContext(EquipmentsPositionContext);
  const { setSelectedEquipmentId } = useContext(EquipmentIdContext) || {};

  const center: LatLngExpression = [-19.151801, -46.007759];

  function getEquipmentById(equipmentId: string) {
    const filteredEquipment = equipmentsStateHistory.filter(
      (equipment) => equipment.equipmentId === equipmentId
    );
    return filteredEquipment[0];
  }

  return (
    <Box h="100vh" w="80%">
      <MapContainer
        center={center}
        zoom={10}
        scrollWheelZoom={true}
        style={{ height: "100%", width: "100%" }}
      >
        <TileLayer
          attribution='&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
          url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
        />

        {equipmentsPositions?.map((equipmentPosition) => {
          const equipment = getEquipmentById(equipmentPosition.equipmentId);
          if (!equipment) return null;

          return (
            <Marker
              key={equipment.equipmentId}
              position={[
                equipmentPosition.positions[equipmentsPositions.length - 1].lat,
                equipmentPosition.positions[equipmentsPositions.length - 1].lon,
              ]}
              eventHandlers={{
                click: () => setSelectedEquipmentId?.(equipment.equipmentId)
              }}
            >
              <Popup>
                <Text as="span" color={equipment.color}>
                  {equipment.name}
                </Text>
              </Popup>
            </Marker>
          );
        })}
      </MapContainer>
    </Box>
  );
}

export default Map;

额外检查

确认Map组件已被包裹在EquipmentIdProvider内部,否则useContext会返回null,导致setSelectedEquipmentId无法调用。


内容的提问来源于stack exchange,提问作者Roger Xavier

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.26 21:32:41