Update in real time tooltip with react-leaflet when changing language with i18n

假如想象 提交于 2020-06-01 05:43:06

问题


I am currently displaying a Map, thanks to react-leaflet, with a GeoJSON Component. I'm also displaying some tooltips on hover over some countries and cities(for example, when I hover France, a tooltip display "France"). I'm also using i18n for internationalization. The internationalization works fine for the country tooltips, they are updated in real time.

I have a function updateDisplay, that switch between a GeoJson component for the countries, or a list of Marker for the cities, on zoom change.

The problem is, that when i'm switching languages, it works fine for the whole page, but not for the city tooltips. They are updated only when I zoom (so when the updateDisplay is called).

I would have the expected behaviour : regardless of the zoom, I would like that the city tooltips update in real time, when i switch language.

I hope I've made myself clear

Here is my code :

/**
 * Display a Leaflet Map, containing a GeoJson object, or a list of Markers, depending on the zoom
 */
export default function CustomMap(): ReactElement {
  const { t }: { t: TFunction } = useTranslation();

  const countryToString = (countries: string[]): string => countries.map(c => t(c)).join(", ");


  // Contains the json containing the polygons of the countries
  const data: geojson.FeatureCollection = geoJsonData as geojson.FeatureCollection;
  let geoJson: JSX.Element = <GeoJSON
    key='my-geojson'
    data={data}
    style={() => ({
      color: '#4a83ec',
      weight: 1,
      fillColor: "#1a1d62",
      fillOpacity: 0.25,
    })}
    onEachFeature={(feature: geojson.Feature<geojson.GeometryObject>, layer: Layer) => {
      layer.on({
        'mouseover': (e: LeafletMouseEvent) => {
          const country = countries[e.target.feature.properties.adm0_a3];
          layer.bindTooltip(countryToString(country.tooltip as string[]));
          layer.openTooltip(country.latlng);
        },
        'mouseout': () => {
          layer.unbindTooltip();
          layer.closeTooltip();
        },
      });
    }}
  />

  // Contains a list of marker for the cities
  const cityMarkers: JSX.Element[] = cities.map(
    (
      c: position,
      i: number
    ) => {
      return (
        // Here are the tooltips that doesn't update in real time, when we switch language
        // FIX ME
        <Marker key={c.latlng.lat + c.latlng.lng} position={c.latlng}>
          <Tooltip>{t(c.tooltip as string)}</Tooltip>
        </Marker>
      );
    }
  );

  const [state, setState] = useState<state>({
    zoom: 3,
    display: geoJson,
  });


  // Update on zoom change
  function onZoom(e: LeafletMouseEvent): void {
    const zoom = e.target._zoom;
    const newDisplay = updateDisplay(zoom);
    setState({
      ...state,
      zoom,
      display: newDisplay,
    });
  }

  // Called on every zoom change, in order to display either the GeoJson, or the cities Marker
  function updateDisplay(zoom: number): Marker[] | any {
    if (zoom >= 4) {
      return cityMarkers;
    } else {
      return geoJson;
    }
  }


  return (
    <Map
      style={{ height: "500px" }}
      center={[54.370138916189596, -29.918133437500003]}
      zoom={state.zoom}
      onZoomend={onZoom}
    >
      <TileLayer url="https://api.mapbox.com/styles/v1/mapbox/streets-v11/tiles/{z}/{x}/{y}?access_token=pk.eyJ1IjoibWFwYm94IiwiYSI6ImNpejY4NXVycTA2emYycXBndHRqcmZ3N3gifQ.rJcFIG214AriISLbB6B5aw" />
      {state.display}
    </Map>
  );
}

You can also look at it here : https://github.com/TheTisiboth/WebCV/blob/WIP/src/components/customMap.tsx
It is on the branch WIP


回答1:


You can do the following to overcome this issue:

  1. Create a boolean flag to keep in memory if the markers have been added
  2. Add the markers on the map using native leaflet code instead of react'leaflet's wrappers.

    • If the markers are added and zoom >= 4 set the flag to true
    • if zoom < 4 remove the markers to be able to show countries, set flag to false
  3. When language is changed, if zoom is bigger, equal than 4 and markers have been added remove the previous, add new ones with the new tooltip

you can achieve all these by holding a reference to the map instance.

Here is the whole code you will need, (parts of cities, markers removed):

import React, { useState, ReactElement } from "react";
import { useTranslation } from "react-i18next";
import { Map, Marker, TileLayer, GeoJSON } from "react-leaflet";
import geoJsonData from "../assets/geoJsonData.json";
import { LatLngLiteral, Layer, LeafletMouseEvent } from "leaflet";
import geojson from "geojson";
import { TFunction } from "i18next";
import L from "leaflet";

interface position {
  latlng: LatLngLiteral;
  tooltip: string;
}

interface state {
  markers: position[];
  zoom: number;
  display: position[] | any;
  geoJson: JSX.Element;
  countries: { [key: string]: position };
}

/**
 * Display a Leaflet Map, containing a GeoJson object, or a list of Markers, depending on the zoom
 */
export default function CustomMap(): ReactElement {
  const mapRef: any = React.useRef();
  const { t, i18n }: { t: TFunction; i18n: any } = useTranslation();
  const [markersAdded, setMarkersAdded] = useState(false);

  i18n.on("languageChanged", (lng: any) => {
    if (lng) {
      const map = mapRef.current;
      if (map && map.leafletElement.getZoom() >= 4 && markersAdded) {
        map.leafletElement.eachLayer(function (layer: L.Layer) {
          if (layer instanceof L.Marker) map.leafletElement.removeLayer(layer);
        });
        state.markers.map((c: position, i: number) => {
          L.marker(c.latlng)
            .addTo(map.leafletElement)
            .bindTooltip(t(c.tooltip));
        });
      }
    }
  });

  // const countryToString = (countries: string[]): string => countries.join(", ");

  // List of position and label of tooltip for the GeoJson object, for each country
  const countries: { [key: string]: position } = {
    DEU: {
      latlng: {
        lat: 51.0834196,
        lng: 10.4234469,
      },
      tooltip: "travel.germany",
    },
    CZE: {
      latlng: {
        lat: 49.667628,
        lng: 15.326962,
      },
      tooltip: "travel.tchequie",
    },
    BEL: {
      latlng: {
        lat: 50.6402809,
        lng: 4.6667145,
      },
      tooltip: "travel.belgium",
    },
  };

  // List of position and tooltip for the cities Markers
  const cities: position[] = [
    {
      latlng: {
        lat: 48.13825988769531,
        lng: 11.584508895874023,
      },
      tooltip: "travel.munich",
    },
    {
      latlng: {
        lat: 52.51763153076172,
        lng: 13.40965747833252,
      },
      tooltip: "travel.berlin",
    },
    {
      // greece
      latlng: {
        lat: 37.99076843261719,
        lng: 23.74122428894043,
      },
      tooltip: "travel.athens",
    },
    {
      // greece
      latlng: {
        lat: 37.938621520996094,
        lng: 22.92695426940918,
      },
      tooltip: "travel.corinth",
    },
  ];

  // Contains the json containing the polygons of the countries
  const data: geojson.FeatureCollection = geoJsonData as geojson.FeatureCollection;
  let geoJson: JSX.Element = (
    <GeoJSON
      key='my-geojson'
      data={data}
      style={() => ({
        color: "#4a83ec",
        weight: 1,
        fillColor: "#1a1d62",
        fillOpacity: 0.25,
      })}
      // PROBLEM : does not update the tooltips when we switch languages
      // FIX ME
      onEachFeature={(
        feature: geojson.Feature<geojson.GeometryObject>,
        layer: Layer
      ) => {
        layer.on({
          mouseover: (e: LeafletMouseEvent) => {
            const country =
              state.countries[e.target.feature.properties.adm0_a3];
            layer.bindTooltip(t(country?.tooltip));
            layer.openTooltip(country?.latlng);
          },
          mouseout: () => {
            layer.unbindTooltip();
            layer.closeTooltip();
          },
        });
      }}
    />
  );

  const [state, setState] = useState<state>({
    markers: cities,
    zoom: 3,
    geoJson: geoJson,
    display: geoJson,
    countries: countries,
  });

  // Update on zoom change
  function onZoom(e: LeafletMouseEvent): void {
    const zoom = e.target._zoom;
    const newDisplay = updateDisplay(zoom);
    setState({
      ...state,
      zoom,
      display: newDisplay,
    });
  }

  // Called on every zoom change, in order to display either the GeoJson, or the cities Marker
  function updateDisplay(zoom: number): Marker[] | any {
    const map = mapRef.current;
    if (zoom >= 4) {
      return state.markers.map((c: position, i: number) => {
        console.log(t(c.tooltip));
        if (map && !markersAdded) {
          console.log(map.leafletElement);
          L.marker(c.latlng)
            .addTo(map.leafletElement)
            .bindTooltip(t(c.tooltip));
          setMarkersAdded(true);
        }
      });
    } else {
      map.leafletElement.eachLayer(function (layer: L.Layer) {
        if (layer instanceof L.Marker) map.leafletElement.removeLayer(layer);
      });
      setMarkersAdded(false);
      return state.geoJson;
    }
  }

  return (
    <Map
      ref={mapRef}
      style={{ height: "500px" }}
      center={[54.370138916189596, -29.918133437500003]}
      zoom={state.zoom}
      onZoomend={onZoom}
    >
      <TileLayer url='https://api.mapbox.com/styles/v1/mapbox/streets-v11/tiles/{z}/{x}/{y}?access_token=pk.eyJ1IjoibWFwYm94IiwiYSI6ImNpejY4NXVycTA2emYycXBndHRqcmZ3N3gifQ.rJcFIG214AriISLbB6B5aw' />
      {state.display}
    </Map>
  );
}

Eng:

"travel": {
    "germany": "Munich, Berlin, Hambourg, Münster, Allemagne",
    "munich": "Munchen",
    "berlin": "Berlin",
    "tchequie": "Tchéquie, Prague",
    "belgium": "Belgique",
    "athens": "Athènes",
    "corinth": "Corinthe",
    ...
 }

Fr:

"travel": {
    "germany": "Munich, Berlin, Hamburg, Münster, Germany",
    "munich": "Munich",
    "berlin": "Berlin",
    "tchequie": "Czech Republic, Prague",
    "belgium": "Belgium",
    "athens": "Athens",
    "corinth": "Corinth",
     ...
 }

You can make it more clean by reusing the markers removal code chunk and markers addition code chunk respectively.



来源:https://stackoverflow.com/questions/61521141/update-in-real-time-tooltip-with-react-leaflet-when-changing-language-with-i18n

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!