React leaflet cannot update state - reactjs

Building a React Leaflet app for first time, what I am trying to do is an onclick event on a layer that would update my state, the idea would be that the coordinates I add will modify state and I can then use this array to form a polygon. For some reasons I cannot make it work at the beginning of the project, this is the simplified code.
When clicking on one of the layers, I can console log all the information of the layer from the event no problem but cannot modify my state so I am thinking I might be rerendering. Can anyone spot the mistake? Thanks
const [coords, setCoords] = useState([])
const onclickevent = e => {
setCoords([...coords, e.target])
}
let givedata = (country, layer) => {
layer.on({click: onclickevent
});
};
<MapContainer className='leaflet-container' center={[48.85078290000001, 2.27537]} zoom={13} style={{ height: "100vh" }}>
<GeoJSON data={quartiersgeojson.features} onEachFeature={givedata}/>
<TileLayer
attribution='&copy OpenStreetMap contributors'
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
/>
</MapContainer>

Related

Fit a map's bounds to contents of a FeatureGroup in React-Leaflet

I have a FeatureGroup with a Circle inside of it. On load, I want the map's viewport to completely show the circle. There are a number of relevant StackOverflow questions (this one, another one, a third), and I have tried them all. None have worked for me using modern tools.
import { MapContainer, TileLayer, FeatureGroup, Circle } from 'react-leaflet'
export default function MyMap({volunteer, pollingPlaces}) {
const coordinates = [40.781753, -73.966583];
const circle = <Circle pathOptions={{color: 'purple'}} center={coordinates} radius={3*1609} />
return (
<MapContainer center={coordinates}
style={{
height:"400px",
width: "400px"
}}>
<TileLayer
attribution='© OpenStreetMap contributors'
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
/>
<FeatureGroup>
{circle}
</FeatureGroup>
</MapContainer>
)
}
One of the big problems is that string refs are now deprecated, and most of the old answers rely on this. It's also not clear to me how to use the new useMap hook in concert with the createRef method in a function component. (The third approach above seems to get the closest, and is from just a year ago, but it still relies on componentDidMount.) Finally, I am unable to get my FeatureGroup to call either the onadd or onAdd method (it's unclear which is correct? see comment below the answer…) when I pass it to the component. The 2018 answer relies on this callback.
The combination of these three issues mean I can't get any of the old solutions working. So I thought I'd ask anew: what's the right way to do this these days?
For the question described in the title, as you have another one later regarding onAdd and it is better to ask it separately in my opinion, you can still use a ref to get a reference to the FeatureGroup
All you need is to call map.fitBounds. To get the map reference you need to use whenCreated prop inside the component that includes MapContainer. If you were inside a child you would have to use useMap hook to get it. Once you get this you need to use the featureGroup ref which is a react ref, an object actually, and can be accessed via current. Inside there you have some leaflet methods. The one you need is getBounds method to retrieve the bounds of your feature group.
const [map, setMap] = useState(null);
const featureGroupRef = useRef();
useEffect(() => {
if (!map) return;
map.fitBounds(featureGroupRef.current.getBounds());
}, [map]);
<MapContainer
center={coordinates}
zoom={6}
style={{
height: "400px",
width: "400px"
}}
whenCreated={setMap}
>
...
<FeatureGroup ref={featureGroupRef}>{circle}</FeatureGroup>
</MapContainer>
Demo

React-Leaflet: Polyline does not change colour despite colour value in Redux store updating

I have setup a Redux store which contains a hex colour code. I plan to implement a function where users can select a colour which the line will appear as. However, when the I used the selector to update the color prop of the polyline, the line on the map itself does not change colour. I have already verified the redux store is working fine.
const DashboardLineAnimation: React.FC<Props> = (): ReactElement => {
const [start, setStart] = useState([0, 0]);
const [end, setEnd] = useState([45, 45]);
return (
<div>
<input onChange={() => {setStart([start[0] + 10, start[1] + 10])}}></input>
<Polyline color={useSelector((state: RootState): string => state.attackMap.options.color.hex)} positions={[start as LatLngTuple, end as LatLngTuple]} />
</div>
);
};
You have to use the documented prop pathOptions which is an object the property color on that object and change the value of that property. color prop is available but normally it should be immutable as it is not documented on the official docs while pathOptions is documented as mutable, thus can be changed.
Also make sure the Polyline is a child of MapContainer when you use it. Here is a demo without using redux with a local state variable
function App() {
...
const [color, setColor] = useState("blue");
const handleClick = () => setColor("red");
return (
<>
<button onClick={handleClick}>Change polyline color</button>
<MapContainer center={position} zoom={13} style={{ height: "100vh" }}>
<TileLayer
attribution='© OpenStreetMap contributors'
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
/>
<Polyline pathOptions={{ color }} positions={polyline} />
</MapContainer>
</>
);
}

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

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>
);
}

Add polyline on button click

I'd like to add a poly line to my map but can't seem to get it to render on button click. Here is the code I currently have:
<button onClick={
function(){
console.log(asset.past);
var pathLine = new L.Polyline(asset.past, {
color: 'red',
weight: 3,
opacity: 0.5,
smoothFactor: 1
}
);
leafletMap.addLayer(pathLine);}
}
className="btn btn-info eachBtn">Go</button>
Thanks, Ed.
It appears that you're trying to manage the map state outside the context of react-leaflet. I'm not even sure if you're using the react-leaflet package or sort of trying to roll your own react website that happens to have leaflet.
If you are indeed using react-leaflet, you should be maintaining a list of polylines that you want to render in the component's state or a property that is updated and sent by a parent component. Then, in your render function you would iterate over these polylines and render each of them as react-leaflet polylines.
Something like this:
render() {
return (
<Map
center={[51.505, -0.09]}
zoom={13}
>
<TileLayer
attribution='© OpenStreetMap contributors'
url='http://{s}.tile.osm.org/{z}/{x}/{y}.png'
/>
{this.state.polylines.map((positions, idx) =>
<Polyline key={`polyline-${idx}`} positions={positions} />
)}
</Map>
);
}
Also, see the example over here of a similar react-leaflet application that adds markers after points are clicked on the map.

Resources