【问题标题】:React-Leaflet does not render Map on the entire state after update更新后 React-Leaflet 不会在整个状态上渲染 Map
【发布时间】:2022-08-14 15:58:59
【问题描述】:

由于某种原因,我的网络应用程序不是完全当状态改变时更新。

让我解释一下:我有一个 React-Leaflet Map,我希望在用户更改过滤条件时更新它。例如,更改某些Markers 出现的城市。为此,我构建了一个从端点加载一些 JSON 的后端,我的网络应用程序获取它,然后成功更新项目存储我需要的数据的数组。在此之后,确实将新标记添加到地图中。

什么不是更新的是我的MapContainerzoomcenter,尽管在安装组件时正确执行了处理此问题的功能。

因此,简而言之,在componentDidMount 内部,我获取数据,然后将其传递给state,用于填充和呈现我的地图。之后,如果用户按下筛选按钮并插入一个新城市我的componentDidUpdate 识别props 已更改,然后获取新数据。尽管如此,我的地图只会重新渲染添加新的标记没有设置新的缩放和新的中心。

有人会好心帮我解决这个问题吗?非常感谢提前。


import React from \"react\";
import { MapContainer, TileLayer } from \"react-leaflet\";
import MyMarker from \"./MyMarker\";
import \"./MapObject.css\";

/*
  IMPORTANT: We are NOT taking into consideration the earth\'s curvature in
             neither getCenter nor getZoom. This is only left as it is for
            the time being because it is not mission critical
*/

// Function to calculate center of the map based on latitudes and longitudes in the array
function getCenter(json) {
  // Array to store latitude and longitude
  var lats = [];
  var lngs = [];
  const arr = Object.keys(json).map((key) => [key, json[key]]);

  // Loop through the array to get latitude and longitude arrays
  for (let i = 0; i < arr.length; i++) {
    lats.push(arr[i][1].Latitude);
    lngs.push(arr[i][1].Longitude);
  }
  const lat = lats.reduce((a, b) => a + b) / lats.length;
  const lng = lngs.reduce((a, b) => a + b) / lngs.length;
  return [lat, lng];
}

// Function to get the zoom level of the map based on the number of markers
function getZoom(json) {
  // Array to store latitude and longitude
  var lats = [];
  var lngs = [];
  const arr = Object.keys(json).map((key) => [key, json[key]]);

  // Loop through the array to get latitude and longitude arrays
  for (let i = 0; i < arr.length; i++) {
    lats.push(arr[i][1].Latitude);
    lngs.push(arr[i][1].Longitude);
  }
  const zoom = Math.floor(Math.log2(lats.length)) + 8;
  return zoom;
}

export default class MapObject extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      map: null,
      dataIsLoaded: false,
      zoom: null,
      position: [null, null],
      items: [],
    };
  }

  changePos(pos) {
    this.setState({ position: pos });
    const { map } = this.state;
    if (map) map.flyTo(pos);
  }

  fetchData(filter, param) {
    fetch(`https://my-backend-123.herokuapp.com/api/v1/${filter}/${param}`)
      .then((res) => res.json())

      .then((json) => {
        this.setState(
          {
            items: json,
            dataIsLoaded: true,
            zoom: getZoom(json),
            position: [getCenter(json)[0], getCenter(json)[1]],
          },
          () => {
            console.log(\"State: \", this.state);
            this.changePos(this.state.position);
          }
        );
      });
    console.log(
      \"Fetched new data: \" +
        \"DataisLoaded: \" +
        this.state.dataIsLoaded +
        \" \" +
        \"Zoom: \" +
        this.state.zoom +
        \" \" +
        \"Lat: \" +
        this.state.position[0] +
        \" \" +
        \"Lng: \" +
        this.state.position[1]
    );
  }

  componentDidMount() {
    this.fetchData(\"city\", this.props.filterValue);
  }

  componentDidUpdate(prevProps) {
    if (prevProps.filterValue !== this.props.filterValue) {
      this.fetchData(\"city\", this.props.filterValue);
      //MapContainer.setCenter([getCenter(this.state.items)[0], getCenter(this.state.items)[1]]);
    }
  }

  render() {
    // Logic to show skeleton loader while data is still loading from the server
    const { dataIsLoaded, items } = this.state;
    console.log(\"Rendering!\");

    if (!dataIsLoaded)
      return (
        <div className=\"bg-white p-2 h-160 sm:p-4 rounded-2xl shadow-lg flex flex-col sm:flex-row gap-5 select-none\">
          <div className=\"h-full sm:h-full sm:w-full rounded-xl bg-gray-200 animate-pulse\"></div>
        </div>
      );

    // Logic to show map and markers if data is loaded
    return (
      <div>
        <MapContainer
          id=\"mapId\"
          whenCreated={(map) => this.setState({ map })}
          attributionControl={true} // remove Leaflet attribution control
          center={this.state.position}
          zoom={this.state.zoom}
          scrollWheelZoom={false}
        >
          <TileLayer
            attribution=\'&copy; <a href=\"https://www.openstreetmap.org/copyright\">OpenStreetMap</a> contributors\'
            url=\"https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png\"
          />
          {this.state.map}
          {items.map((item, index) => (
            <div key={index}>
              <MyMarker
                name={item.Name}
                city={item.City}
                prov={item.Province}
                lat={item.Latitude}
                lng={item.Longitude}
                phone={item[\"Phone Number\"]}
              />
            </div>
          ))}
        </MapContainer>
      </div>
    );
  }
}


我在下面留下一个 GIF 来展示当前的行为。您还可以简单地注意到标记“消失”:这是因为新标记已在考虑新过滤器的情况下呈现,这就是为什么我随后手动缩小以显示新标记确实已经渲染了。

Demo of the bug

编辑: 我现在正在尝试实现this 解决方案,但到目前为止我还没有成功。

  • 你使用什么版本的 react-leaflet?你能创建一个与来自后端的模拟数据相同的演示吗?

标签: reactjs leaflet react-leaflet react-state


【解决方案1】:

好吧,我决定完全重构我的代码,并从我之前的 class 组件切换到 function 组件。

我实现的解决方案来自this answer

关键是将以下函数添加到我的代码中:

/*
 Function to move the map to the center of the markers after the map
 is loaded with new data and with the correct zoom.
*/
function FlyMapTo(props) {
  const map = useMap();

  useEffect(() => {
    map.flyTo(props.center, props.zoom);
  });

  return null;
}

之后,我将此组件添加到负责渲染组件的 JSX:

return (
  <MapContainer center={center} zoom={zoom} scrollWheelZoom={true}>
    <TileLayer
      attribution='&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
      url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
    />
    {items.map((item, index) => (
      <div key={index}>
        <MyMarker
          name={item.Name}
          city={item.City}
          prov={item.Province}
          lat={item.Latitude}
          lng={item.Longitude}
          phone={item["Phone Number"]}
        />
      </div>
    ))}
    <FlyMapTo center={center} zoom={zoom} />
  </MapContainer>
);

仅供参考,以防有人遇到类似问题,我将添加我的新 Map.js 组件的完整代码:

import React, { useState, useEffect } from "react";
import "./MapObject.css";
import { MapContainer, TileLayer } from "react-leaflet";
import MyMarker from "./MyMarker";
import { useMap } from "react-leaflet";

/*
  IMPORTANT: We are NOT taking into consideration the earth's curvature in
             neither getCenter nor getZoom. This is only left as it is for
            the time being because it is not mission critical
*/

// Function to calculate center of the map based on latitudes and longitudes in the array
function getCenter(json) {
  // Array to store latitude and longitude
  var lats = [];
  var lngs = [];
  const arr = Object.keys(json).map((key) => [key, json[key]]);

  // Loop through the array to get latitude and longitude arrays
  for (let i = 0; i < arr.length; i++) {
    lats.push(arr[i][1].Latitude);
    lngs.push(arr[i][1].Longitude);
  }
  const lat = lats.reduce((a, b) => a + b) / lats.length;
  const lng = lngs.reduce((a, b) => a + b) / lngs.length;
  return [lat, lng];
}

// Function to get the zoom level of the map based on the number of markers
function getZoom(json) {
  // Array to store latitude and longitude
  var lats = [];
  var lngs = [];
  const arr = Object.keys(json).map((key) => [key, json[key]]);

  // Loop through the array to get latitude and longitude arrays
  for (let i = 0; i < arr.length; i++) {
    lats.push(arr[i][1].Latitude);
    lngs.push(arr[i][1].Longitude);
  }
  const zoom = Math.floor(Math.log2(lats.length)) + 8;
  return zoom;
}

// Function to move the map to the center of the markers after the map is loaded with new data
function FlyMapTo(props) {
  const map = useMap();

  useEffect(() => {
    map.flyTo(props.center, props.zoom);
  });

  return null;
}

export default function Map(props) {
  const [zoom, setZoom] = useState(null);
  const [center, setCenter] = useState([null, null]);
  const [items, setItems] = useState([]);
  const [dataIsLoaded, setDataIsLoaded] = useState(false);

  useEffect(() => {
    fetch(
      `https://pandi-bi-backend.herokuapp.com/api/v1/${props.filterType}/${props.filterValue}`
    )
      .then((res) => res.json())
      .then((json) => {
        if (json["detail"] === "Not Found") {
          setItems([]);
          setDataIsLoaded(false);
        } else {
          setItems(json);
          setDataIsLoaded(true);
          setCenter(getCenter(json));
          setZoom(getZoom(json));
        }
      });
  }, [props.filterType, props.filterValue]);

  // If data is not loaded, show an empty map
  if (!dataIsLoaded && items.length === 0) {
    return (
      <MapContainer
        center={{ lat: 45.0, lng: 12.0 }}
        zoom={5}
        scrollWheelZoom={true}
      >
        <TileLayer
          attribution='&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
          url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
        />
      </MapContainer>
    );
    // If data is loaded, show the map with markers
  } else if (dataIsLoaded) {
    return (
      <MapContainer center={center} zoom={zoom} scrollWheelZoom={true}>
        <TileLayer
          attribution='&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
          url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
        />
        {items.map((item, index) => (
          <div key={index}>
            <MyMarker
              name={item.Name}
              city={item.City}
              prov={item.Province}
              lat={item.Latitude}
              lng={item.Longitude}
              phone={item["Phone Number"]}
            />
          </div>
        ))}
        <FlyMapTo center={center} zoom={zoom} />
      </MapContainer>
    );
  }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-05-19
    • 2022-01-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-01-28
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多