Next js with react-leaflet window is not defined when refreshing page - reactjs

im using react-leaflet in Next js but when reloading page shows "window is not defined" even i am using dynamic import with ssr:false,
i saw this question made by others here and tried answers that they offered but didn't work, also tried to make the map mounted after component but again no result,
my code:
function ContactPage() {
const MapWithNoSSR = dynamic(() => import('../Map/Map'), {
ssr: false,
})
return (
<div className={styles.map}>
<MapWithNoSSR/>
</div>
)
}
function Map(){
const [markers, setMarkers]= useState([
{cord:[12.3774729,20.446257]},
{cord:[45.3774729,10.45224757]},
{cord:[40.3774729,15.4364757]},
])
<MapContainer center={[12.374729,22.4464757]} zoom={13} scrollWheelZoom={true} style={{height: "100%", width: "100%",zIndex:'1'}}>
<TileLayer
url={`example.com`}
attribution='Map data © <a>Mapbox</a>'
/>
{markers?.map((element,idx)=>{
return <Marker
position={element?.cord}
draggable={true}
animate={true}
key={idx}
>
<Popup>
Test PopUP
</Popup>
</Marker>
})}
</MapContainer>
}}
}

as you were told in the comment, dynamic () has to go outside of the component or screen you are going to return, e. g.
import dynamic from "next/dynamic"
const MyAwesomeMap = dynamic(() => import("../components/Map/index"), { ssr:false })
export default function inicio() {
return(
<>
<p>Example Map</p>
<MyAwesomeMap />
</>
)
}

Your Map component doesn't return anything

Related

TypeError: Cannot read properties of undefined (reading '_leaflet_events')

I'm using leaflet v1.8.0 and react-leaflet v4.0.1 in my Next.js app. I have a map container component and marker group components inside it. Previously it was running fine but after some database changes in the backend and fixing the frontend accordingly, I started getting error like this whenever I try to load the page with the map container component.
Unhandled Runtime Error
TypeError: Cannot read properties of undefined (reading '_leaflet_events')
Call Stack
addOne
node_modules\leaflet\dist\leaflet-src.esm.js (2789:0)
on
node_modules\leaflet\dist\leaflet-src.esm.js (2718:0)
NewClass._addFocusListenersOnLayer
node_modules\leaflet\dist\leaflet-src.esm.js (10894:0)
NewClass._addFocusListeners
node_modules\leaflet\dist\leaflet-src.esm.js (10887:0)
NewClass.fire
node_modules\leaflet\dist\leaflet-src.esm.js (599:0)
NewClass._layerAdd
node_modules\leaflet\dist\leaflet-src.esm.js (6827:0)
NewClass.whenReady
node_modules\leaflet\dist\leaflet-src.esm.js (4583:0)
I have a map container component CameraSitesMap.js like this:
import { MapContainer, TileLayer, ZoomControl } from 'react-leaflet'
import 'leaflet/dist/leaflet.css'
import 'leaflet-defaulticon-compatibility/dist/leaflet-defaulticon-compatibility.css'
import 'leaflet-defaulticon-compatibility'
import 'leaflet-area-select'
...
return (
<div css={styles.mapContainer}>
{isLoading ? (
<div css={styles.loader}>
<Spinner />
</div>
) : (
<MapContainer
preferCanvas={true}
bounds={mapBounds}
zoomControl={false}
scrollWheelZoom={false}
doubleClickZoom={false}
boxZoom={false}
style={{ height: '100%', width: '100%' }}
attributionControl={false}
>
<ZoomControl position="bottomleft" />
{cameraView || isExploreMode() ? (
<AreaSelector setSelectionArea={setSelectionArea} />
) : null}
<TileLayer
attribution='<img src="https://www.onemap.gov.sg/docs/maps/images/oneMap64-01.png" style="height:20px;width:20px;"/> OneMap | Map data © contributors, Singapore Land Authority'
url="https://maps-{s}.onemap.sg/v3/Default/{z}/{x}/{y}.png"
/>
{!isExploreMode() ? (
!cameraView ? (
<SiteLocations />
) : (
<CameraLocations />
)
) : (
<>
<SiteLocations />
<CameraLocations />
</>
)}
</MapContainer>
)}
</div>
)
which basically renders a map into the page component with some view toggling logic for different marker groups: <SiteLocations /> and <CameraLocations />.
And this is what the SiteLocations.js looks like:
import { Circle, Tooltip, useMap } from 'react-leaflet'
...
const SiteLocations = () => {
const map = useMap()
...
useEffect(() => {
setMap(map) // just storing instance in zustand store for another component
}, [])
const processedSitesData = isExploreMode() ? exploringSite : allSites;
return (
<>
{processedSitesData?.map((site) => {
const id = site?.id
const { name } = site?.attributes
const { lat, lng, area } = site?.attributes
const position = [lat, lng]
// console.log(id, name, lat, lng, area)
// none of these logs are undefined
return (
<Circle
center={position}
radius={area}
weight={1}
color="blue"
key={id}
css={styles.circleMarker}
eventHandlers={{
click: () => !isExploreMode() && setExploreSite(site),
}}
>
{!isExploreMode() && (
<Tooltip>This is a placeholder.</Tooltip> // The error doesn't occur and the circle is correctly rendered if I replace this line with <></>.
)}
</Circle>
)
})}
</>
)
}
If I remove the Tooltip, the error is gone and the Circle itself renders perfectly. Previously before the database changes, this was working fine. So I tried logging the data used in the component, but they all have correct values and are not undefined. I have tried removing node_modules and reinstalling with yarn in case the package wasn't installed properly. I have also tried Clear Site Data in browser. The error still happens if I add Tooltip. Please help.
I found the solution, everyone. Thanks for your time.
I had to update my react-leaflet and leaflet package versions. It seems to be a bug in the Leaflet library itself and solved in leaflet v1.9.2 according to the release notes: https://github.com/Leaflet/Leaflet/releases

Using react-leaflet-geosearch in React Leaflet: cannot read property addControl

Trying to add react-leaflet-geosearch to my leaflet map and I get the error:
Cannot read property 'addControl' of undefined
I do not know what the problem seems to be:
My code:
render() {
const prov = OpenStreetMapProvider();
const GeoSearchControlElement = SearchControl;
return (
<>
<MapContainer
style={{ height: "100%", width: "100%" }}
center={position}
zoom="0"
scrollWheelZoom={true}
>
<TileLayer
attribution='© OpenStreetMap contributors'
url="https://api.mapbox.com/styles/v1/<style>/tiles/256/{z}/{x}/{y}#2x?access_token=<token>"
/>
<Marker position={position} ></Marker>
<ChangeMapView coords={position} />
<GeoSearchControlElement
provider={prov}
showMarker={true}
showPopup={false}
popupFormat={({ query, result }) => result.label}
maxMarkers={3}
retainZoomLevel={false}
animateZoom={true}
autoClose={false}
searchLabel={"Enter address, please"}
keepResult={true}
/>
</MapContainer>
</>
);
You seem to use React Leaflet version 3 since you have a <MapContainer> component.
Unfortunately, the react-leaflet-geosearch plugin you are trying to integrate was made for React Leaflet version 2: https://github.com/TA-Geoforce/react-leaflet-geosearch/blob/master/package.json#L108
Due to the big API changes between these major versions, it is unlikely it will be compatible.
react-leaflet has been re-designed and useLeaflet hook is no longer available in v3 API which makes SearchControl component not compatible with v3
Until react-leaflet-geosearch compatibility with react-leaflet v3 is implemented, the following SearchControl could be considered instead (compatible with react-leaflet v3)
const SearchControl = (props) => {
const map = useMap();
useEffect(() => {
const searchControl = new GeoSearchControl({
provider: props.provider,
...props,
});
map.addControl(searchControl);
return () => map.removeControl(searchControl);
}, [props]);
return null;
};
Usage
class MapComponent extends React.Component {
render() {
const { position, zoom } = this.props;
const prov = OpenStreetMapProvider();
return (
<MapContainer center={position} zoom={zoom}>
<TileLayer
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
attribution='© OpenStreetMap contributors'
/>
<SearchControl
provider={prov}
showMarker={true}
showPopup={false}
popupFormat={({ query, result }) => result.label}
maxMarkers={3}
retainZoomLevel={false}
animateZoom={true}
autoClose={false}
searchLabel={"Enter address, please"}
keepResult={true}
/>
</MapContainer>
);
}
}
Live demo

React-bootstrap cards not wrapping

My Code is:
import React, { useEffect, useState } from "react";
import styles from "./Cards.module.css";
import { CardDeck, Card } from "react-bootstrap";
const Cards = ({ animeArray }) => {
const [aanimeArray, setAnimeArray] = useState([]);
useEffect(() => {
setAnimeArray(animeArray);
}, [animeArray]);
if (!aanimeArray) {
return;
}
console.log("Anime Array", aanimeArray);
return (
<div className={styles.container}>
{aanimeArray === [] ? (
<h1>Search</h1>
) : (
<CardDeck>
{aanimeArray.map((anime) => {
return (
<Card>
<Card.Img variant = "top" src={anime.image_url} />
<Card.Body>
<Card.Title>{anime.title}</Card.Title>
</Card.Body>
<Card.Footer>
<small className="text-muted">{anime.rated}</small>
</Card.Footer>
</Card>
);
})}
</CardDeck>
)}
</div>
);
};
export default Cards;
I am not using any custom styling whatsoever.
The result of the above mentioned code is as seen on this image:
Image of the issue
You have to make the effort of making them wrap. In fact, as seen on the documentation, majority of the developers' examples includes the CSS property width with a value of 18rem.
Here is an example by leveraging minWidth:
const sampleStyle = {
minWidth: "20%",
flexGrow: 0
};
<Card style={sampleStyle}>
First thing.
aanimeArray === []
won't work since you are comparing an array with another array.
Best way in Javascript for this is to check the length of the array.
aanimeArray.length === 0
means it is an empty array.
About the styling I think you need to show us the CSS code as well. I'm not sure what CardDeck component does...

zoom in dynamically to fit all the marker React-leaflet

I'm using react-leaflet. to show the map in my react app. I'm also showing marker on the map too. The problem is the zoom level is not appropriate because sometimes the marker might be quite near to each other and sometimes they will be very far apart. Is there any way to set the zoom level depending on the distance of the markers so that the user can see all the markers at once?
Here is my code
<Map center={center} maxZoom={9} zoom={5}>
<MarkerClusterGroup showCoverageOnHover={false}>
{
markers.map(({fillColor, position, id}) =>
<CircleMarker fillColor={fillColor} color={darken(0.1, fillColor)} radius={10} fillOpacity={1} key={id} center={position} onClick={this.onClick} />
}
</MarkerClusterGroup>
</Map>
P.S: My react-leaflet version is 2.4.0
Assuming MarkerClusterGroup is a component from react-leaflet-markercluster package, the following example demonstartes how to auto-zoom to cover visible markers:
function CustomLayer(props) {
const groupRef = useRef(null);
const { markers } = props;
const mapContext = useLeaflet();
const { map} = mapContext; //get map instance
useEffect(() => {
const group = groupRef.current.leafletElement; //get leaflet.markercluster instance
map.fitBounds(group.getBounds()); //zoom to cover visible markers
}, []);
return (
<MarkerClusterGroup ref={groupRef} showCoverageOnHover={false}>
{markers.map(({ fillColor, position, id }) => (
<CircleMarker
fillColor={fillColor}
radius={10}
fillOpacity={1}
key={id}
center={position}
/>
))}
</MarkerClusterGroup>
);
}
Usage
function MapExample(props) {
const { markers, center } = props;
return (
<Map center={center} maxZoom={9} zoom={5}>
<TileLayer
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
attribution='© OpenStreetMap contributors'
/>
<CustomLayer markers={markers} />
</Map>
);
}

Access Gatsby Component from a function

I am trying to access a Gatsby component (Anime) from outside of it.
Can not figure out what instance name this would have or how to name it.
Here is my code:
import React from 'react'
import PropTypes from 'prop-types'
import PreviewCompatibleImage from '../components/PreviewCompatibleImage'
import Anime from 'react-anime';
import VisibilitySensor from 'react-visibility-sensor';
function onChange (isVisible) {
console.log('Element is now %s', isVisible ? 'visible' : 'hidden')
}
const FeatureGrid = ({ gridItems }) => (
<div className="columns is-multiline">
<VisibilitySensor onChange={onChange}>
<Anime delay={(e, i) => i * 100}
scale={[.1, .9]}
autoplay={false}>
{gridItems.map(item => (
<div key={item.text} className="column is-3">
<section className="section">
<div className="has-text-centered">
<div
style={{
width: '160px',
display: 'inline-block',
}}
>
<PreviewCompatibleImage imageInfo={item} />
</div>
</div>
<p>{item.text}</p>
</section>
</div>
))}
</Anime>
</VisibilitySensor>
</div>
)
FeatureGrid.propTypes = {
gridItems: PropTypes.arrayOf(
PropTypes.shape({
image: PropTypes.oneOfType([PropTypes.object, PropTypes.string]),
text: PropTypes.string,
})
),
}
export default FeatureGrid
I want to get the animation to trigger from the onChange function.
How do I get the name or set the name of the Anime component so I can access it from the function?
Or is there another way I should address this?
Using a Gatsby starter netlify CMS as the base, so extending on their code, but seems that const is not the route I should take.
I want the animation to trigger when it becomes visible.
Any suggestions?
According to the docs react-visibility-sensor :
You can pass a child function, which can be convenient if you don't need to store the visibility anywhere
so maybe instead of using the onchange function you can just pass the isVisible parameter, something like:
<VisibilitySensor>
{({isVisible}) =>
<Anime delay={(e, i) => i * 100}
// the rest of your codes here ...
</Anime>
}
</VisibilitySensor>
Otherwise you can convert this function to a react component and set states, etc..

Resources