From bc778bfd976c026e2011277369e1d937be8f8e2f Mon Sep 17 00:00:00 2001 From: vjrj Date: Tue, 21 Jul 2026 18:45:15 +0200 Subject: [PATCH] =?UTF-8?q?deps:=20react-leaflet=201.8=20->=204.2=20(+=20l?= =?UTF-8?q?eaflet=201.9)=20=E2=80=94=20the=20last=20legacy=20react-*=20lib?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ground-up hooks rewrite of the app's core map. Removes the final batch of React-19-blocking warnings (legacy context on Map/LayersControl/TileLayer/ Marker/CircleMarker/Circle/Tooltip + ReactDOM.render from the old controls). Core: -> ; .leafletElement refs (6 files) -> the map/ layer instances directly, via a child (useMap) + plain refs on Marker/Circle/GeoJSON; controlled viewport (onViewportChanged + state.center/ zoom) -> (useMapEvents moveend/zoomend) + imperative setView; onClick -> eventHandlers={{click}}; Path styling -> pathOptions/style; subsUnion takes the L.Map directly. The 4 v1-only plugins reimplemented (new in-repo helpers under Maps/): - react-leaflet-control -> MapControl (L.Control + createPortal) - react-leaflet-google -> GoogleMutantLayer (createLayerComponent + leaflet.gridlayer.googlemutant; Google Maps API already loaded by Gkeys) - react-leaflet-fullscreen -> createControlComponent + leaflet.fullscreen - leaflet-sleep / leaflet-graphicscale kept as vanilla (work on leaflet 1.9) Browser-verified: /fires (tiles, OSM+Google layer switch, custom control, fullscreen, graphic scale, pan/zoom -> re-fetch, no NaN), fire detail (GeoJSON rect + fitBounds), home (3 maps coexist; SelectionMap draggable marker -> updatePosition + distance circle). Console now shows 0 React warnings on home/fires. REST smoke byte-identical. --- PENDIENTE.md | 9 +- UPGRADE.md | 4 +- imports/ui/components/Maps/DefMapLayers.js | 8 +- imports/ui/components/Maps/FireCircleMark.js | 2 +- imports/ui/components/Maps/FireIconMark.js | 15 +-- imports/ui/components/Maps/FirePixel.js | 5 +- imports/ui/components/Maps/FirePolygonMark.js | 2 +- imports/ui/components/Maps/FullScreenMap.js | 34 +++--- .../ui/components/Maps/GoogleMutantLayer.js | 25 ++++ imports/ui/components/Maps/MapBridge.js | 22 ++++ imports/ui/components/Maps/MapControl.js | 32 +++++ .../ui/components/Maps/SubsUnion/SubsUnion.js | 7 +- .../components/SelectionMap/SelectionMap.js | 67 +++++------ imports/ui/pages/Fires/Fires.js | 46 ++++---- imports/ui/pages/FiresMap/FiresMap.js | 60 ++++++---- .../pages/Subscriptions/SubscriptionsMap.js | 38 +++--- package-lock.json | 111 ++++++------------ package.json | 9 +- 18 files changed, 276 insertions(+), 220 deletions(-) create mode 100644 imports/ui/components/Maps/GoogleMutantLayer.js create mode 100644 imports/ui/components/Maps/MapBridge.js create mode 100644 imports/ui/components/Maps/MapControl.js diff --git a/PENDIENTE.md b/PENDIENTE.md index 86ca6a5..8dc4a60 100644 --- a/PENDIENTE.md +++ b/PENDIENTE.md @@ -18,7 +18,7 @@ desarrollo (no en producción) y **bloquean el salto a React 19**. Estado: | react-bootstrap | 0.31 → 2 | ✅ hecho (CSS sigue en Bootstrap 4) | | reactstrap + 3 deps muertas | — | ✅ eliminadas (0 usos) | | react-router-dom | 4.2 → 6.30 | ✅ hecho (history 5, HOC `withRouterCompat`) | -| **react-leaflet + leaflet** | 1.8/1.3 → 4/1.9 | ⏸️ **diferida** (mapa = feature central; 4 plugins v1-only sin equivalente v4; ver UPGRADE.md) | +| react-leaflet + leaflet | 1.8/1.3 → 4.2/1.9 | ✅ hecho (Map→MapContainer, refs directas, useMap/useMapEvents; 4 plugins reimplementados: MapControl portal, GoogleMutantLayer, fullscreen, sleep/graphicscale vanilla; ver UPGRADE.md) | | **Bootstrap CSS/JS** | 4.1 → 5 | ⏸️ **diferida** (carrusel/navbar jQuery BS4; ver UPGRADE.md) | Lo verificado en navegador tras cada lib: `/`, `/fires`, `/fire/archive/`, @@ -28,9 +28,10 @@ parámetros por defecto; `UNSAFE_componentWillReceiveProps` (FromNow, FireStats, Fires, SelectionMap) → `getDerivedStateFromProps`/`componentDidUpdate`; Reconnect (banner Blaze `meteorStatus` con `findDOMNode`) → React nativo con `useTracker(Meteor.status)`; react-share 2→5 y react-progress-bar.js → progressbar.js -(ambos tiraban `findDOMNode`/`defaultProps`). **Tras esto, la ÚNICA fuente de -warnings en consola es react-leaflet + sus 4 plugins (diferida).** (Queda un -`findDOMNode` menor solo en `/status`: el `` de Status.js, página admin.) +(ambos tiraban `findDOMNode`/`defaultProps`); y react-leaflet 1.8→4.2 (mapa central, +4 plugins reimplementados). **Tras esto la consola tiene 0 warnings de React en las +rutas principales** (verificado forzando re-render en / y /fires). Único resto para +React 19: el `` de Status.js (`findDOMNode`), solo en `/status` (admin). --- diff --git a/UPGRADE.md b/UPGRADE.md index baa7e63..f71fdbb 100644 --- a/UPGRADE.md +++ b/UPGRADE.md @@ -308,7 +308,7 @@ MONGO_CONTAINER=tcef-mongo7 MONGO_SHELL=mongosh MONGO_PORT=27019 ./smoke/smoke.s | Dep | From | Target | Status | |---|---|---|---| -| React / react-dom | 16.0 | 18 | ✅ done — 18.3.1 + createRoot. The peripheral react-* libs that only worked on 18 via legacy context are now all modernized (rows below) **except react-leaflet** (deferred). Our own React-19 blockers are also cleared: `defaultProps` on function components → default params; `UNSAFE_componentWillReceiveProps` → `getDerivedStateFromProps`/`componentDidUpdate`; the Blaze/findDOMNode `Reconnect` → native React; react-share 2→5 and react-progress-bar.js → progressbar.js (both dropped their findDOMNode/defaultProps). **Sole remaining console warnings are react-leaflet + its 4 plugins** — nothing else. | +| React / react-dom | 16.0 | 18 | ✅ done — 18.3.1 + createRoot. The peripheral react-* libs that only worked on 18 via legacy context are now all modernized (rows below) **except react-leaflet** (deferred). Our own React-19 blockers are also cleared: `defaultProps` on function components → default params; `UNSAFE_componentWillReceiveProps` → `getDerivedStateFromProps`/`componentDidUpdate`; the Blaze/findDOMNode `Reconnect` → native React; react-share 2→5 and react-progress-bar.js → progressbar.js (both dropped their findDOMNode/defaultProps). react-leaflet 1.8→4.2 done too (row below). **Dev console is now 0 React warnings** on the main routes. The only React-19 leftover is the Blaze `serverFacts` on the /status admin page (findDOMNode via gadicc:blaze-react-component). | | react-share | 2.0 | 5.3 | ✅ done — v2 shipped function-component `defaultProps` (React-19 blocker). v5 is clean. Dropped the dead GooglePlus button (removed upstream in v4+); the other 6 share buttons are API-compatible. | | react-progress-bar.js | 0.2.3 | — (progressbar.js 1.1) | ✅ removed — the wrapper used the deprecated `findDOMNode`. `LoadingBar` now drives `progressbar.js` (its own underlying dep, promoted to direct) through a ref. | | gadicc:blaze-react-component (in Reconnect) | — | native React | ✅ Reconnect's Blaze `meteorStatus` bridge used `findDOMNode` and was always mounted → rewritten with `useTracker(Meteor.status)` + countdown effect, reusing 255kb:meteor-status's CSS. (Status.js's Blaze `serverFacts` kept — /status admin page only.) | @@ -320,7 +320,7 @@ MONGO_CONTAINER=tcef-mongo7 MONGO_SHELL=mongosh MONGO_PORT=27019 ./smoke/smoke.s | react-meteor-data | 0.2.16 | 3.0.6 | ✅ done — 0.2.16 (React-15/16-era) looped `withTracker` on fire-detail pages under React 18 (never-ready → blank). `meteor add react-meteor-data@3.0.6`; no call-site changes (HOC API kept). Also dropped `tmeasday:check-npm-versions` (the constraint pinning 0.2.16). See section above. | | i18next | 10.5 | 23.16 | ✅ done — with react-i18next 14. `whitelist`→`supportedLngs`, added `compatibilityJSON: 'v3'` (our locale JSON uses natural-language keys + v3 `_plural` layout, not the v4 `_one`/`_other` suffixes), custom separators `ß`/`ð`/`đ` kept. `sendMissing`→`saveMissing`, missingKeyHandler signature is `(lngs, ns, key, fallbackValue)` now (array first). See react-i18next row. | | react-i18next | 7.4 | 14.1 | ✅ done — 48 `translate([], {wait:true})`/`translate()` HOCs → `withTranslation()`; `react.wait:true`→`react.useSuspense:false`. `` unchanged (locale JSON already stores the indexed-tag format `<1><0>{{x}}`). ReSendEmail: removed `` (deleted in v10) → ``, dropped the removed bare `t` export. This is what silenced the per-component `Translate`/`I18n` legacy-context console spam. Backends: xhr→`i18next-http-backend` (client), sync-fs→`i18next-fs-backend` (server); `i18next-localstorage-cache` dropped (was `enabled:false`); languagedetector 2→8. Browser-verified: es/en switch, `` interpolation (`{{countTotal}}`+``) renders on /fires. | -| react-leaflet + leaflet | 1.8 / 1.3.1 | 4.x / 1.9 | ⏸️ **deferred as debt** (the one lib not modernized this pass). react-leaflet v4 is a ground-up hooks rewrite of the app's core feature (the fire map), and the migration is deeply entangled: (a) **4 unmaintained v1-only plugins with no v4 equivalent** — `react-leaflet-control` (custom map buttons, 3 maps), `react-leaflet-fullscreen`, `react-leaflet-google` (`GoogleLayer` base layers in DefMapLayers), `leaflet-sleep` (`sleep`/`wakeTime`… props on ``) — each needs a bespoke `createControlComponent`/`createLayerComponent`/googlemutant reimplementation; (b) the **controlled-viewport** pattern (`onViewportChanged` + `state.center/zoom` in FiresMap/SelectionMap) is gone in v4 (`MapContainer` center/zoom are initial-only; you drive it via a `useMap()` child); (c) `.leafletElement` refs in 6 files/13 sites (FiresMap, SelectionMap, SubscriptionsMap, Fires, SubsUnion) — v4 hands you the `L.Map`/layer directly with different timing; (d) `Leaflet.control.graphicScale().addTo(map)` + `subsUnion` reach into the raw map via the ref. **v1.8 works on React 18** (verified: all maps render), so this only blocks the eventual React 19 jump. Do it as its own focused project with heavy in-browser verification (drag markers, viewport-driven fire reload, fitBounds, layer switch, fullscreen, sleep, custom controls). Stays pinned at 1.8 until then. **After the rest of the front-end modernization + warning cleanup, react-leaflet + these 4 plugins are the SOLE remaining source of dev-console warnings** (legacy context on Map/LayersControl/TileLayer/Marker/CircleMarker/Circle/Tooltip and the plugins, plus `ReactDOM.render` from react-leaflet-control). | +| react-leaflet + leaflet | 1.8 / 1.3.1 | 4.2.1 / 1.9.4 | ✅ done — the last and hardest lib; a ground-up hooks rewrite of the app's core map. ``→``; `.leafletElement` refs (6 files/13 sites) → the map/layer instances directly (via a `` child using `useMap`, and plain refs on Marker/Circle/GeoJSON); the controlled-viewport pattern (`onViewportChanged`+`state.center/zoom`) → `` (`useMapEvents` moveend/zoomend) + imperative `setView`; `onClick`→`eventHandlers={{click}}`, path styling → `pathOptions`/`style`; `subsUnion` takes the `L.Map` directly. **The 4 v1-only plugins were reimplemented** with react-leaflet v4's factories / vanilla Leaflet plugins: `react-leaflet-control`→ in-repo `MapControl` (L.Control + createPortal), `react-leaflet-google`→ `GoogleMutantLayer` (`createLayerComponent` + `leaflet.gridlayer.googlemutant`; Google Maps API already loaded by Gkeys), `react-leaflet-fullscreen`→ `createControlComponent` + `leaflet.fullscreen`, `leaflet-sleep`/`leaflet-graphicscale` kept as vanilla (work on leaflet 1.9). Browser-verified: /fires (tiles, OSM+Google layer switch, custom control, fullscreen, graphic scale, pan/zoom→re-fetch pipeline, no NaN), fire detail (GeoJSON rect + fitBounds), home (all 3 maps coexist; SelectionMap draggable marker→updatePosition + distance circle). **Console now 0 warnings** across home/`/fires` (forced full re-render). REST smoke byte-identical. | | arkham:comments-ui | 1.4.x | — | ✅ replaced with in-repo React feature | | nimble:restivus | Atmosphere | vendored | ✅ local precompiled package, accounts-password 2.x | | maximum:server-transform, meteorhacks:zones, less, markdown, test stack | — | — | ✅ removed (dead/unused) | diff --git a/imports/ui/components/Maps/DefMapLayers.js b/imports/ui/components/Maps/DefMapLayers.js index 0355394..022f298 100644 --- a/imports/ui/components/Maps/DefMapLayers.js +++ b/imports/ui/components/Maps/DefMapLayers.js @@ -5,7 +5,7 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; import { withTranslation } from 'react-i18next'; -import { GoogleLayer } from 'react-leaflet-google/lib/'; +import GoogleMutantLayer from '/imports/ui/components/Maps/GoogleMutantLayer'; import Gkeys from '/imports/startup/client/Gkeys'; import { TileLayer, LayersControl } from 'react-leaflet'; @@ -54,15 +54,15 @@ class DefMapLayers extends Component { {/* React.Fragment does not work here */} { this.state.gkey && - + } { this.state.gkey && - + } { this.state.gkey && - + } ); diff --git a/imports/ui/components/Maps/FireCircleMark.js b/imports/ui/components/Maps/FireCircleMark.js index 80b9826..7d0d6e0 100644 --- a/imports/ui/components/Maps/FireCircleMark.js +++ b/imports/ui/components/Maps/FireCircleMark.js @@ -31,7 +31,7 @@ class FireCircleMark extends Component { } = this.props; const rect = rectangleAround({ lat, lon }, track, track); return ( - + ); diff --git a/imports/ui/components/Maps/FireIconMark.js b/imports/ui/components/Maps/FireIconMark.js index d63b1e7..034c1fe 100644 --- a/imports/ui/components/Maps/FireIconMark.js +++ b/imports/ui/components/Maps/FireIconMark.js @@ -33,9 +33,9 @@ class FireIconMark extends Component { lat, lon, scan, track, nasa, id, history, falsePositives, industries, neighbour, when, t } = this.props; return ( -
+ { !falsePositives && !industries && - + } { industries && @@ -43,23 +43,20 @@ class FireIconMark extends Component { } { /* disabled */ industries && false && } { falsePositives && !industries && - + {t('Es una industria (fuente: nuestros usuarios/as)')} { /* disabled because was a past fire (and can be marked multiple times) */ false && } } { !falsePositives && !industries && - + } -
+ ); } } diff --git a/imports/ui/components/Maps/FirePixel.js b/imports/ui/components/Maps/FirePixel.js index da78185..d09213a 100644 --- a/imports/ui/components/Maps/FirePixel.js +++ b/imports/ui/components/Maps/FirePixel.js @@ -12,11 +12,8 @@ const FirePixel = ({ }) => ( diff --git a/imports/ui/components/Maps/FirePolygonMark.js b/imports/ui/components/Maps/FirePolygonMark.js index 92e3640..1b76bde 100644 --- a/imports/ui/components/Maps/FirePolygonMark.js +++ b/imports/ui/components/Maps/FirePolygonMark.js @@ -29,7 +29,7 @@ class FirePolygonMark extends Component { const lon = centerid.coordinates[0]; const lat = centerid.coordinates[1]; return ( - + ); /* */ } diff --git a/imports/ui/components/Maps/FullScreenMap.js b/imports/ui/components/Maps/FullScreenMap.js index 8738d19..543fd2c 100644 --- a/imports/ui/components/Maps/FullScreenMap.js +++ b/imports/ui/components/Maps/FullScreenMap.js @@ -1,25 +1,27 @@ /* eslint-disable react/jsx-indent-props */ /* eslint-disable import/no-absolute-path */ -/* eslint-disable import/no-absolute-path */ -import React, { Component } from 'react'; +import React from 'react'; import PropTypes from 'prop-types'; import { withTranslation } from 'react-i18next'; -import FullscreenControl from 'react-leaflet-fullscreen'; -import 'react-leaflet-fullscreen/dist/styles.css'; +import { createControlComponent } from '@react-leaflet/core'; +import L from 'leaflet'; +import 'leaflet.fullscreen'; +import 'leaflet.fullscreen/Control.FullScreen.css'; -class FullScreenMap extends Component { - render() { - const { t } = this.props; - return ( - - ); - } -} +// Replacement for react-leaflet-fullscreen (v1-only): wrap the vanilla +// leaflet.fullscreen control with react-leaflet v4's control factory. +const FullscreenControl = createControlComponent( + ({ position = 'topleft', title, titleCancel }) => L.control.fullscreen({ position, title, titleCancel }) +); + +const FullScreenMap = ({ t }) => ( + +); FullScreenMap.propTypes = { t: PropTypes.func.isRequired diff --git a/imports/ui/components/Maps/GoogleMutantLayer.js b/imports/ui/components/Maps/GoogleMutantLayer.js new file mode 100644 index 0000000..d2b07e7 --- /dev/null +++ b/imports/ui/components/Maps/GoogleMutantLayer.js @@ -0,0 +1,25 @@ +import { createLayerComponent } from '@react-leaflet/core'; +import L from 'leaflet'; +import 'leaflet.gridlayer.googlemutant'; + +// Replacement for react-leaflet-google's GoogleLayer (v1-only). The Google Maps +// JS API is already loaded (with the places library) by Gkeys before any Google +// BaseLayer renders, so googlemutant can use window.google.maps directly. +function createGoogleMutant({ + maptype = 'roadmap', opacity, googlekey, ...options +}, ctx) { + const instance = L.gridLayer.googleMutant({ + type: String(maptype).toLowerCase(), + ...(opacity != null ? { opacity } : {}), + ...options + }); + return { instance, context: { ...ctx } }; +} + +function updateGoogleMutant(instance, props, prevProps) { + if (props.opacity != null && props.opacity !== prevProps.opacity) { + instance.setOpacity(props.opacity); + } +} + +export default createLayerComponent(createGoogleMutant, updateGoogleMutant); diff --git a/imports/ui/components/Maps/MapBridge.js b/imports/ui/components/Maps/MapBridge.js new file mode 100644 index 0000000..ea44ea2 --- /dev/null +++ b/imports/ui/components/Maps/MapBridge.js @@ -0,0 +1,22 @@ +import { useEffect } from 'react'; +import { useMap, useMapEvents } from 'react-leaflet'; + +// v4 replaces the v1 `ref`/`.leafletElement` + handleLeafletLoad pattern: this +// child runs inside and hands the ready Leaflet map to a class +// parent once, so the parent can drive it imperatively (setView, fitBounds, +// graphicScale, subsUnion, …). +export const MapReady = ({ onReady }) => { + const map = useMap(); + useEffect(() => { + if (onReady) onReady(map); + // run once per map instance + }, [map]); // eslint-disable-line react-hooks/exhaustive-deps + return null; +}; + +// Bridges Leaflet map events to callbacks (replaces the v1 onMoveend / +// onViewportChanged / onZoomend props on ). +export const MapEvents = ({ handlers }) => { + useMapEvents(handlers || {}); + return null; +}; diff --git a/imports/ui/components/Maps/MapControl.js b/imports/ui/components/Maps/MapControl.js new file mode 100644 index 0000000..ce69571 --- /dev/null +++ b/imports/ui/components/Maps/MapControl.js @@ -0,0 +1,32 @@ +import { useEffect, useState } from 'react'; +import { createPortal } from 'react-dom'; +import { useMap } from 'react-leaflet'; +import L from 'leaflet'; + +// Replacement for react-leaflet-control (v1-only): mounts a Leaflet control at +// the given corner and portals arbitrary React children into it. Click/scroll +// on the control no longer pans/zooms the map underneath. +const MapControl = ({ position = 'topright', children }) => { + const map = useMap(); + const [container, setContainer] = useState(null); + + useEffect(() => { + const ReactControl = L.Control.extend({ + onAdd: () => { + const div = L.DomUtil.create('div', 'leaflet-control leaflet-control-react'); + L.DomEvent.disableClickPropagation(div); + L.DomEvent.disableScrollPropagation(div); + setContainer(div); + return div; + }, + onRemove: () => setContainer(null) + }); + const control = new ReactControl({ position }); + control.addTo(map); + return () => control.remove(); + }, [map, position]); + + return container ? createPortal(children, container) : null; +}; + +export default MapControl; diff --git a/imports/ui/components/Maps/SubsUnion/SubsUnion.js b/imports/ui/components/Maps/SubsUnion/SubsUnion.js index 41a4d81..c2f4216 100644 --- a/imports/ui/components/Maps/SubsUnion/SubsUnion.js +++ b/imports/ui/components/Maps/SubsUnion/SubsUnion.js @@ -12,7 +12,8 @@ const subsUnion = (union, options) => { const interactive = options.interactive || false; if (options.subs) { - const lmap = options.map.leafletElement; + // v4: options.map is the Leaflet map instance directly (no .leafletElement) + const lmap = options.map; if (union) { lmap.removeLayer(union); } @@ -30,7 +31,7 @@ const subsUnion = (union, options) => { if (options.fit && options.bounds) { // console.log(options.bounds); const bounds = JSON.parse(options.bounds); - options.map.leafletElement.fitBounds(L.latLngBounds(bounds._northEast, bounds._southWest)); + lmap.fitBounds(L.latLngBounds(bounds._northEast, bounds._southWest)); } } else if (options.subs.length > 0) { const result = calcUnion(L, options.subs, sub => sub, true); @@ -43,7 +44,7 @@ const subsUnion = (union, options) => { }); union.addTo(lmap); if (options.fit) { - options.map.leafletElement.fitBounds(bounds); + lmap.fitBounds(bounds); } } } diff --git a/imports/ui/components/SelectionMap/SelectionMap.js b/imports/ui/components/SelectionMap/SelectionMap.js index 9726a31..7be7a98 100644 --- a/imports/ui/components/SelectionMap/SelectionMap.js +++ b/imports/ui/components/SelectionMap/SelectionMap.js @@ -7,7 +7,7 @@ import React, { Component, Fragment } from 'react'; import PropTypes from 'prop-types'; import { Meteor } from 'meteor/meteor'; -import { Map, Marker, CircleMarker, Circle, Tooltip } from 'react-leaflet'; +import { MapContainer, Marker, CircleMarker, Circle, Tooltip } from 'react-leaflet'; import Leaflet from 'leaflet'; import { withTranslation } from 'react-i18next'; import { withTracker } from 'meteor/react-meteor-data'; @@ -18,7 +18,8 @@ import DefMapLayers from '/imports/ui/components/Maps/DefMapLayers'; import 'leaflet-graphicscale/dist/Leaflet.GraphicScale.min.css'; import 'leaflet-graphicscale/dist/Leaflet.GraphicScale.min.js'; import 'leaflet-sleep/Leaflet.Sleep.js'; -import Control from 'react-leaflet-control'; +import MapControl from '/imports/ui/components/Maps/MapControl'; +import { MapReady, MapEvents } from '/imports/ui/components/Maps/MapBridge'; import { Row, Col, Button, ButtonGroup } from 'react-bootstrap'; import subsUnion from '/imports/ui/components/Maps/SubsUnion/SubsUnion'; import UserSubsToFiresCollection from '/imports/api/Subscriptions/Subscriptions'; @@ -51,13 +52,16 @@ class SelectionMap extends Component { this.fit = this.fit.bind(this); this.addScale = this.addScale.bind(this); this.onFstBtn = this.onFstBtn.bind(this); - this.onViewportChanged = this.onViewportChanged.bind(this); + this.onMapMove = this.onMapMove.bind(this); + this.handleMapReady = this.handleMapReady.bind(this); } - componentDidMount() { - if (this.isValidState()) { - this.addScale(); - } + // v4: MapReady hands us the ready Leaflet map (replaces the componentDidMount + // + ref/.leafletElement wiring) + handleMapReady(map) { + this.map = map; + if (this.isValidState()) this.addScale(); + this.handleLeafletLoad(map); } // Was UNSAFE_componentWillReceiveProps: pull center/distance from props into @@ -75,6 +79,8 @@ class SelectionMap extends Component { marker: center[0] ? center : this.state.marker, distance: distance || this.state.distance }); + // v4 map is uncontrolled: move it imperatively on a real center change + if (centerChanged && this.map) this.map.setView(center, this.state.zoom); } this.fit(); } @@ -90,7 +96,11 @@ class SelectionMap extends Component { this.props.onSndBtn(); } - onViewportChanged(viewport) { + // v4: fed by on moveend/zoomend (was the v1 onViewportChanged prop) + onMapMove() { + if (!this.map) return; + const c = this.map.getCenter(); + const viewport = { center: [c.lat, c.lng], zoom: this.map.getZoom() }; if (this.props.onViewportChanged) { this.props.onViewportChanged(viewport); } @@ -100,7 +110,7 @@ class SelectionMap extends Component { } getMap() { - return this.selectionMap.leafletElement; + return this.map; } toggleDraggable() { @@ -108,7 +118,7 @@ class SelectionMap extends Component { } updatePosition() { - const { lat, lng } = this.marker.leafletElement.getLatLng(); + const { lat, lng } = this.marker.getLatLng(); // console.log(`New marker lat ${lat} and lng ${lng}`); const currentDistance = this.state.distance; this.setState(update(this.state, { $merge: { marker: [lat, lng] } })); @@ -122,10 +132,10 @@ class SelectionMap extends Component { // console.log("fit!"); if (this.props.currentSubs.length > 0 && this.state.subsFit && this.props.action !== action.add) { // has autofit, do nothing - } else if (this.selectionMap && this.distanceCircle) { - if (!this.getMap().getBounds().contains(this.distanceCircle.leafletElement.getBounds())) { + } else if (this.map && this.distanceCircle) { + if (!this.getMap().getBounds().contains(this.distanceCircle.getBounds())) { // console.log('New area circle not visible'); - this.getMap().fitBounds(this.distanceCircle.leafletElement.getBounds()); // padding , [70, 70]); + this.getMap().fitBounds(this.distanceCircle.getBounds()); // padding , [70, 70]); } else { // console.log('New area circle visible'); } @@ -133,7 +143,7 @@ class SelectionMap extends Component { } addScale() { - if (this.selectionMap) { + if (this.map) { // https://www.npmjs.com/package/leaflet-graphicscale const map = this.getMap(); const options = { @@ -170,16 +180,10 @@ class SelectionMap extends Component { this.isValidState() ? - { - this.selectionMap = map; - this.handleLeafletLoad(map); - }} + + + {this.props.action === action.edit && this.props.currentSubs.map((subs, index) => ( @@ -198,7 +204,7 @@ class SelectionMap extends Component { position={[subs.location.lat, subs.location.lon]} icon={removeIcon} title={t('Pulsa para borrar')} - onClick={() => { onRemove(subs._id); }} + eventHandlers={{ click: () => { onRemove(subs._id); } }} > {index === 0 && { this.distanceCircle = ref; }} - color="#145A32" - fillColor="green" - fillOpacity={0.1} + pathOptions={{ color: '#145A32', fillColor: 'green', fillOpacity: 0.1 }} radius={this.state.distance * 1000} /> } - + { this.props.sndBtn && this.props.onSndBtn &&