Rendering a MapContainer on several subpages results in error - reactjs

I'm having some issues with MapContainer from react-leaflet throwing errors at me in my app.
I have a tabel of events, that links to a page with event details, this details page implements a map with the following component:
export function MapWithPoints(props: Props) {
const { points } = props
return (
<MapContainer scrollWheelZoom={false} style={{ height: 300, width: '100vw' }}>
<InnerMap points={points} />
</MapContainer>
)
}
InnerMap is defined as:
function InnerMap(props: Props) {
const { points } = props
const leafletMap = useMap()
React.useEffect(() => {
if (leafletMap && points.length) {
const pointArray: L.LatLngTuple[] = points.map((item) => [
item.lat,
item.lng,
])
const bounds = L.latLngBounds(pointArray)
leafletMap.fitBounds(bounds)
}
}, [points, leafletMap])
return (
<React.Fragment>
<TileLayer
attribution='© OpenStreetMap contributors'
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
/>
{points
.filter((poi) => poi.lat && poi.lng)
.map((poi) => (
<Marker
position={[poi.lat, poi.lng]}
key={`${poi.id}_${poi.lat}_${poi.lng}`}
icon={poi.icon ? poi.icon : DefaultIcon}
>
{poi.description ? <Popup>{poi.description}</Popup> : null}
</Marker>
))}
</React.Fragment>
)
}
The map works when I go to the first (random) entry in my list of events, the event page loads like it should, but if I go back and then choose another event page, MapContainer throws an error:
Uncaught Error: Map container is already initialized.
at NewClass._initContainer (Map.js:1092:1)
at NewClass.initialize (Map.js:136:1)
at new NewClass (Class.js:24:1)
at MapContainer.js:31:1
at commitAttachRef (react-dom.development.js:23645:1)
at safelyAttachRef (react-dom.development.js:22891:1)
at reappearLayoutEffectsOnFiber (react-dom.development.js:23545:1)
at reappearLayoutEffects_complete (react-dom.development.js:24838:1)
at reappearLayoutEffects_begin (react-dom.development.js:24826:1)
at commitLayoutEffects_begin (react-dom.development.js:24649:1)
I'm using react 18, and react-leaflet 4.0.0
Is there something I have missed in the setup?

There is a bug in 4.0.0, but even when using the latest version, I still get the error now and then. A workaround is to have a unique key on Map Container, for example timestamp. But this result in a memory leak, as each time a map is loaded it consumes more memory

Related

polylinedacorator with react leaflet 4

I am trying to include arrows to the Polyline in react-leaft. For that I am using polylinedecorator plugin. There is a similar post on this platform. However, it uses withLeaflet module which is not supported in react-leaflet 4.0. How can I make it run without using 'withLeaflet'.
I have tried to implement it with the hooks. However, it does not work and need some assistance, how can I make it run.
export default function App(): JSX.Element {
const polylineRef = useRef<any>(null);
const arrow = [
{
offset: "100%",
repeat: 0,
symbol: L.Symbol.arrowHead({
pixelSize: 15,
polygon: false,
pathOptions: { stroke: true }
})
}];
useEffect(()=>{
L.polylineDecorator(polylineRef.current,{
patterns: arrow
})
}, [polylineRef]);
return (
<MapContainer center={center} zoom={13} scrollWheelZoom={true} style={{height: 'calc(100% - 30px)'}}>
<TileLayer
attribution='© OpenStreetMap contributors'
url='https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png'
/>
{currentData?.movingActors.map(line =>(<Polyline key={line.id}
positions={[line.startLocation, line.endLocation] } ref={polylineRef}
color={modeColor(line.mode)}
/>
))}
</MapContainer>
</>);}
CHANGES MADE TO THE ACCEPTED ANSWER TO MAKE IT RUN
function PolylineDecorator({ patterns, polyline,color }) {
const map = useMap();
useEffect(() => {
if (!map) return;
L.polyline(polyline, {color}).addTo(map); // added color property
L.polylineDecorator(polyline, {
patterns,
}).addTo(map);
}, [map]);
return null;
}
{currentData?.movingActors.map(line =>(<PolylineDecorator key={line.id} patterns ={arrow} polyline={position} color = {modeColor(line.mode)} />) ) } //here I used color parameters to dynamically add colors
What you need is a custom react functional component that returns null and has a useEffect with the code to initialize the plugin:
function PolylineDecorator({ patterns, polyline }) {
const map = useMap();
useEffect(() => {
if (!map) return;
L.polyline(polyline).addTo(map);
L.polylineDecorator(polyline, {
patterns
}).addTo(map);
}, [map]);
return null;
}
and then use it like:
<MapContainer...>
<TileLayer url="http://{s}.tile.osm.org/{z}/{x}/{y}.png" />
<PolylineDecorator patterns={arrow} polyline={polyline} />
</MapContainer>
Demo

Show vector layers through Leaflet.MagnifyingGlass on react-leaflet 4

I am implementing the 'Leaflet.MagnifyingGlass' on my typescript react-leaflet v4 project. I am able to show the magnifying glass on the tile layer however, I am not sure, how to implement it on the already overlaid vector layer.
function Lense (){
const lenseLayer = useMap();
const magnifiedTiles = L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png")
useEffect(() => {
L.magnifyingGlass({
layers: [magnifiedTiles]
//layers: [PolylineDecorator]
}).addTo(lenseLayer)
}, [lenseLayer]);
return <></>;
}
export default function App(): JSX.Element {
<MapContainer center={center} zoom={13} scrollWheelZoom={true} style={{height: 'calc(100% - 30px)'}}>
<TileLayer
attribution='© OpenStreetMap contributors'
url='https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png' />
{currentData?.movingActors.map(line =>(<PolylineDecorator key={line.id}
polyline={positions} color={modeColor(line.mode)}/>))}
<Lense />
</MapContainer>
</>
);
}
As one can see, that at the moment, magnifying glass only is laid to tile object. I need to lay it on top of the polylindedecorator object. The function is defined as
function PolylineDecorator({ polyline,color }) {
const map = useMap();
useEffect(() => {
if (!map) return;
var x = L.polyline(polyline, {color}).arrowheads({ size: '5%' });
x.addTo(map);
}, [map]);
return null;
}
Any suggestions, how can I achieve that.
PS I have already tried react-leaflet-magnifying-glass, but it is not compatible with the latest react-leaflet version.

react-leaflet typeError when trying to render coordinates

I'm working on a React project with Redux / thunk and I have this map from react-leaflet :
import { MapContainer, Marker, Popup, TileLayer, Circle } from "react-leaflet";
import { useSelector } from "react-redux";
function Maps() {
const user = useSelector((state: any) => state.userReducer.getUserById);
const coords: [number, number] = [user ? user.latitude : 0, user ? user.longitude : 0];
const fillBlueOptions = { fillColor: "blue" };
const zone = 10000;
return (
<MapContainer className="maps" center={coords} zoom={10} scrollWheelZoom={false}>
<TileLayer
attribution='© OpenStreetMap contributors'
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
/>
<Circle center={coords} pathOptions={fillBlueOptions} radius={zone} />
<Marker position={coords}>
<Popup>Adress</Popup>
</Marker>
</MapContainer>
);
}
export default Maps;
I get the coordinates of the user with an axios call :
export const getUserById = (id_user: number | string) => {
return (dispatch: any) => {
dispatch(isLoading(true));
return axios
.get(`users/getUserById/${id_user}`)
.then((res) => {
dispatch(isLoading(false));
dispatch({ type: GET_USER_BY_ID, payload: res.data });
})
.catch((err) => {
console.log(err);
dispatch(isLoading(false));
});
};
};
The dispatch is launched here when i click on this table :
<tbody>
{users.slice(0, 20).map((item: any, index: number) => (
<tr
className="redirectUser"
onClick={() => {
history.push("/Utilisateurs");
dispatch(getUserById(item.id));
}}
key={index}
>
<td>{item.id}</td>
<td>
{item.nom} {item.prenom}
<br></br>Tcheker
</td>
<td>{item.email}</td>
<td>18/10/1998</td>
<td>{item.tel}</td>
</tr>
))}
</tbody>
When I try to display the map component on a user who has the coords, i have this error (or the map just show blank screen) :
Uncaught TypeError: Cannot read property 'latitude' of undefined
Then if i go back in the history and re-click on the table, the map finally displays with no error.
When the user doesn't have any coords I get this error :
Uncaught TypeError: Cannot read property 'lng' of null
I understand that the component seems to render before getting the datas, but even with a ternary operator that gives a hard-coded value in case of an undefined answer, the component doesn't render neither.
I think this is an async issue. The very nature of your coords variable looks to be non-blocking and hence the render is performed before the ternary can be performed.
Since coords is just a variable (and not state driven) it never re-renders.
If you could set the state at the time coords is calculated and then refer you're leaflet map to the variables in state, I think you could get it working.
Good luck,
Phil

Markers not working with google-map-react

I'm working with the 'google-map-react' library and I have tried all but the markers are not showing up.
I pass the coords to the marker in many ways but none worked.
Here's my code & repository:
https://github.com/jorginyu/ubica
import React, { Component } from 'react';
import GoogleMapReact from 'google-map-react';
const API_KEY = 'WTFULOOKINAT';
const contacts = [
{ name: 'Spiderman', lat: 41.529616, lng: 2.434130 },
{ name: 'Iron Man', lat: 41.528103, lng: 2.433834 },
{ name: 'Hulk', lat: 41.530192, lng: 2.422994 }
];
const MarkersC = (text ) => <div className="contact">{text}</div>;
export default class MapComponent extends Component {
constructor(props) {
super(props);
this.state = {
center: {
lat: 41.528452,
lng: 2.434195
},
zoom: 18
}
}
render() {
return (
// Important! Always set the container height explicitly
<div className="mt-5" style={{ height: '80vh', width: '100%' }}>
<GoogleMapReact
bootstrapURLKeys={{ key: API_KEY }}
defaultCenter={this.state.center}
defaultZoom={this.state.zoom}
>
{contacts.map((contact,i) => {
<MarkersC position={{lat: contact.lat, lng: contact.lng}} text={contact.name} key={i} />
})}
</GoogleMapReact>
</div>
);
}
}
What can I do? Thanks for your time :)
There is a formatting problem. I deleted the spaces:
THEN:
{
contacts.map((contact, i) =>
<MarkersC lat={contact.lat} lng={contact.lng} text={contact.name} key={i} />
)
}
NOW:
{
contacts.map((contact, i) => <MarkersC lat={contact.lat} lng={contact.lng} text={contact.name} key={i} /> )
}
If you open the browser's console, you will see an error. The problem is with your MarkerC component and how you try to get the text prop.
The parameter of the component is an object with all properties that are passed to it.
You do not destructure it to get the text you simply use the whole parameter and try to display it.
So you need to propertly destructure it as const MarkersC = ( {text} ) => ..
Instead of
const MarkersC = ( text ) => <div className="contact">{text}</div>;
it should be
const MarkersC = ( {text} ) => <div className="contact">{text}</div>;
Update
Just noticed, the google-map-react expect to find lat and lng properties on the marker. You have wrapped them inside a position property so they cannot be found.
So your usage should be
either
<MarkersC lat={contact.lat} lng={contact.lng} text={contact.name} key={i} />
or spread the whole contact object that holds those properties
<MarkersC {...contact} key={i} />
so that the lat,lng and text are all direct properties of the MarkersC component.
Declaring the object type and referencing directly the value of 'text' on the initial 'const' worked for me:
const AnyReactComponent = (text:any) => <div>{text.text}</div>;
In your question, you have provided invalid GoogleMap key so it is showing error in console Google Maps JavaScript API error: InvalidKeyMapError.
So provide a valid GoogleMap key or blank(const API_KEY = '') for development use only.
In my case with blank API_KEY, it's working fine.
(Your git repo code is different than code you have posted here in StackOverflow.)

How to get a ref to the dom node of a google map's marker in react?

Using the google-maps-react npm package I can get a ref to the map's dom node like so:
loadMap() {
const maps = this.props.google.maps;
const mapRef = this.refs.map; <---- ref set in render function on dom node
const node = ReactDOM.findDOMNode(mapRef); <--- the dom node
...
this.map = new maps.Map(node, mapConfig);
this.mapRef = mapRef;
}
It's pretty easy because mapref is set in the render method:
render() {
return (
<div style={{ height: mapHeight }} ref="map">
...
And then that is used to set node and then that is used to new up the map.
How would I do this with a map's marker? A marker doesn't need a dom node to be created and therefore I cant get a ref to the marker.
this.marker = new google.maps.Marker({someOptions}); <----- no dom node needed
I want to do this because I need to dynamically change the icon of the marker based on some value in my redux store. I have tried changing the icon via props (see below), but it somehow prevents the icon marker from being draggable even though draggable is set to true.
return (
<Marker
key={foo}
position={latLngPos}
icon={ active ? activeIcon : defaultIcon }
draggable={true}
onDragstart={() => { return this.handleMapMarkerDragStart();}}
onDragend={() => { return this.handleMapMarkerDrop();}}
/>);
I suspect things are acting strangely because to get google's maps api to work with react, the components have to deal with the actual dom nodes instead of the virtual dom nodes.
Any insight into this would be much appreciated.
Regarding
I want to do this because I need to dynamically change the icon of the
marker based on some value in my redux store. I have tried changing
the icon via props (see below), but it somehow prevents the icon
marker from being draggable even though draggable is set to true.
the following example demonstrates how to:
set marker as draggable
set custom icon per marker
Example
const handleDragEnd = (props, marker, event) => {
console.log(event.latLng);
};
const defaultIcon =
"http://maps.google.com/mapfiles/kml/pushpin/blue-pushpin.png";
const activeIcon =
"http://maps.google.com/mapfiles/kml/pushpin/pink-pushpin.png";
const MapWrapper = props => {
return (
<div className="map-container">
<Map
google={props.google}
className={"map"}
zoom={4}
initialCenter={{ lat: -24.9923319, lng: 135.2252427 }}
>
{props.places.map((place, i) => {
const active = i % 2 === 0;
return (
<Marker
icon={active ? activeIcon : defaultIcon}
key={place.id}
position={{ lat: place.lat, lng: place.lng }}
draggable={true}
onDragend={handleDragEnd}
/>
);
})}
</Map>
</div>
);
};
Note: Marker component does not support onDragstart event
listener at the moment in google-maps-react library, but it could be
attached directly to Google Maps Marker object
Demo

Resources