close popup react-leaflet after user click on button in popup - reactjs

So basically want to make custom close for react-leaflet Popup component, seams that is not a big problem to do with native API leaflet but with react component from react-leaflet I can't find the solution.

at the moment, the only way I found to close the popup is the following:
constructor(props){
super(props);
this.popup = React.createRef();
}
// the magic
closePopusOnClick(){
this.popup.current.leafletElement.options.leaflet.map.closePopup();
}
render(){
return <Marker position={[this.props.lat, this.props.lng]}>
<Popup ref={this.popup}>
<Button onClick={this.closePopusOnClick}>Close popup</Button>
</Popup>
</Marker>;
}
Hope it helps!

In "react-leaflet": "^3.0.2" I managed to close the popup with:
popupRef.current._closeButton.click()
Not very nice comparing to a future Popup.close() method which MUST work out-of-box, but gets the job done...

I ended up with a similar solution to Luca's Answer, so I thought I'd add it as an answer too. I needed to close all popups when moving or zooming the map and ended up with the following:
import React, { useRef } from "react";
import { Map } from "react-leaflet"
export default () => {
const mapRef = useRef(null);
const closePopups = () => {
mapRef.current.leafletElement.closePopup();
};
const handleOnDragend = e => {
closePopups();
};
const handleOnZoomend = e => {
closePopups();
};
if (typeof window === 'undefined') {
return null;
}
return (
<Map
ref={mapRef}
onDragend={handleOnDragend}
onZoomend={handleOnZoomend}
>
</Map>
)
}
This can, however, be extended so that anything can call the closePopups method.

I found the working solution for react-leaflet v3 by modifying these two links codesandbox https://codesandbox.io/s/4ws0i and https://stackoverflow.com/a/67750291/8339172
here is the function to hide the Popup component
const hideElement = () => {
if (!popupElRef.current || !map) return;
map.closePopup();
};
here is the Popup component
<Popup ref={popupElRef} closeButton={false}>
<button onClick={hideElement}>Close popup</button>
</Popup>

Related

Inject Props to React Component

For security reasons, I have to update ant design in my codebase from version 3 to 4.
Previously, this is how I use the icon:
import { Icon } from 'antd';
const Demo = () => (
<div>
<Icon type="smile" />
</div>
);
Since my codebase is relatively big and every single page use Icon, I made a global function getIcon(type) that returns <Icon type={type}>, and I just have to call it whenever I need an Icon.
But starting from antd 4, we have to import Icon we want to use like this:
import { SmileOutlined } from '#ant-design/icons';
const Demo = () => (
<div>
<SmileOutlined />
</div>
);
And yes! Now my getIcon() is not working, I can't pass the type parameter directly.
I tried to import every icon I need and put them inside an object, and call them when I need them. Here's the code:
import {
QuestionCircleTwoTone,
DeleteOutlined,
EditTwoTone
} from '#ant-design/icons';
let icons = {
'notFound': <QuestionCircleTwoTone/>,
'edit': <EditTwoTone/>,
'delete': <DeleteOutlined/>,
}
export const getIcon = (
someParam: any
) => {
let icon = icons[type] !== undefined ? icons[type] : icons['notFound'];
return (
icon
);
};
My problem is: I want to put someParam to the Icon Component, how can I do that?
Or, is there any proper solution to solve my problem?
Thanks~
You can pass props as follows in the icons Object:
let icons = {
'notFound':(props:any)=> <QuestionCircleTwoTone {...props}/>,
'edit': (props:any)=><EditTwoTone {...props}/>,
'delete':(props:any)=> <DeleteOutlined {...props}/>,
}
And then if you will pass any prop to the Icon component then it will pass the prop to the specific icon component
let Icon = icons[type] !== undefined ? icons[type] : icons['notFound'];
return (<Icon someParam={'c'}/>)

Custom button on the leaflet map with React-leaflet version3

I'm a new leaflet learner with React typescript. Want to create a custom button on the map. On clicking the button a popup will appear. I saw many example but they are all based on older version and I also tried to create my own but no luck. The documentation also not providing much help. Even a functional custom control component is also very effective for my app. Any help on this will be much appreciated. Here is my code,
Custom button
import React, { Component } from "react";
import { useMap } from "react-leaflet";
import L, { LeafletMouseEvent, Map } from "leaflet";
class Description extends React.Component<{props: any}> {
createButtonControl() {
const MapHelp = L.Control.extend({
onAdd: (map : Map) => {
const helpDiv = L.DomUtil.create("button", ""); //how to pass here the button name and
//other property ?
//a bit clueless how to add a click event listener to this button and then
// open a popup div on the map
}
});
return new MapHelp({ position: "bottomright" });
}
componentDidMount() {
const { map } = this.props as any;
const control = this.createButtonControl();
control.addTo(map);
}
render() {
return null;
}
}
function withMap(Component : any) {
return function WrappedComponent(props : any) {
const map = useMap();
return <Component {...props} map={map} />;
};
}
export default withMap(Description);
The way I want to call it
<MapContainer
center={defaultPosition}
zoom={6}
zoomControl={false}
>
<Description />
<TileLayer
attribution="Map tiles by Carto, under CC BY 3.0. Data by OpenStreetMap, under ODbL."
url="https://cartodb-basemaps-{s}.global.ssl.fastly.net/light_all/{z}/{x}/{y}.png"
/>
<ZoomControl position={'topright'}/>
</MapContainer>
You're close. Sticking with the class component, you just need to continue creating your buttons instance. You can use a prop on Description to determine what your button will say and do:
<Description
title={"My Button Title"}
markerPosition={[20.27, -157]}
description="This is a custom description!"
/>
In your decsription's createButtonControl, you're almost there. You just need to fill it out a bit:
createButtonControl() {
const MapHelp = L.Control.extend({
onAdd: (map) => {
const helpDiv = L.DomUtil.create("button", "");
this.helpDiv = helpDiv;
// set the inner content from the props
helpDiv.innerHTML = this.props.title;
// add the event listener that will create a marker on the map
helpDiv.addEventListener("click", () => {
console.log(map.getCenter());
const marker = L.marker()
.setLatLng(this.props.markerPosition)
.bindPopup(this.props.description)
.addTo(map);
marker.openPopup();
});
// return the button div
return helpDiv;
}
});
return new MapHelp({ position: "bottomright" });
}
Working codesandbox
There's a million ways to vary this, but hopefully that will get you going.

How can I reset a dragged component to its original position with react-draggable?

I try to implement a function in my app that allows the user to reset all the components that he dragged around to be reset to their original position.
I assume that this functionality exists in react-draggable because of this closed and released issue: "Allow reset of dragging position" (https://github.com/idanen/react-draggable/issues/7). However I did not find any hint in the documentation (https://www.npmjs.com/package/react-draggable).
There was one question with the same content in stackoverflow, but it has been removed (https://stackoverflow.com/questions/61593112/how-to-reset-to-default-position-react-draggable).
Thanks for your help :-)
The referenced issue on the GitHub references a commit. After taking a look at the changes made in this commit, I found a resetState callback added to the useDraggable hook. In another place in the commit, I found a change to the test file which shows usage of the hook.
function Consumer(props) {
const {
targetRef,
handleRef,
getTargetProps,
resetState,
delta,
dragging
} = useDraggable(props);
const { style = defaultStyle } = props;
return (
<main
className='container'
ref={targetRef}
data-testid='main'
style={style}
{...getTargetProps()}
>
{dragging && <span>Dragging to:</span>}
<output>
{delta.x}, {delta.y}
</output>
<button className='handle' ref={handleRef}>
handle
</button>
<button onClick={resetState}>reset</button>
</main>
);
}
The hook returns a set of callbacks, including this callback, which can be used to reset the state of the draggable.
I wanted the component to reset back to its original position when the component was dropped.
Using hooks I monitored if the component was being dragged and when it was false reset the position otherwise it would be undefined.
export default function DraggableComponent(props: any) {
const {label} = props
const [isDragging, setIsDragging] = useState<boolean>(false)
const handleStart = (event: any, info: DraggableData) => {
setIsDragging(true)
}
const handleStop = (event: any, info: DraggableData) => {
setIsDragging(false)
}
return (
<Draggable
onStart={handleStart}
onStop={handleStop}
position={!isDragging? { x: 0, y: 0 } : undefined}
>
<Item>
{label}
</Item>
</Draggable>
)
}
Simple approach would be:
creating a new component to wrap our functionality around the Draggable callbacks
reset position when onStop callback is triggered
Example:
import { useState } from 'react';
import Draggable, { DraggableData, DraggableEvent, DraggableProps } from 'react-draggable';
export function Drag({ children, onStop, ...rest }: Partial<DraggableProps>) {
const initial = { x: 0, y: 0 }
const [pos, setPos] = useState(initial)
function _onStop(e: DraggableEvent, data: DraggableData){
setPos(initial)
onStop?.(e, data)
}
return (
<Draggable position={pos} onStop={_onStop} {...rest}>
{children}
</Draggable>
)
}
Usage:
export function App() {
return (
<Drag> Drag me </Drag>
)
}
Note that this answer does not work.
None of these approaches worked for me, but tobi2424's post on issue 214 of the Draggable repo did. Here's a minimal proof-of-concept:
import React from "react";
import Draggable from "react-draggable";
const DragComponent = () => {
// Updates the drag position parameter passed to Draggable
const [dragPosition, setDragPosition] = React.useState(null);
// Fires when the user stops dragging the element
const choiceHandler = () => {
setDragPosition({x: 0, y: 0});
};
return (
<Draggable
onStop={choiceHandler}
position={dragPosition}
>
Drag me
</Draggable>
);
};
export default DragComponent;
Edit
The code above works intermittently but not particularly well. As far as I can work out, react-draggable stores data about the position of the dragged element somewhere outside of React, in order to preserve the position of the element between component refreshes. I was unable to determine how to reset the position of the element on command and none of the other example code solves the problem for me.
You can do this in a very haphazard manner. There may be another way to set state more safely on this but I didn't look too deeply into it.
import React from 'react';
export default class 😊 extends Component {
constructor(props) {
super(props);
this.draggableEntity = React.createRef();
}
resetDraggable() {
try {
this.draggableEntity.current.state.x = 0;
this.draggableEntity.current.state.y = 0;
} catch (err) {
// Fail silently
}
}
render() {
return (
<Draggable
ref={this.draggableEntity}
>
<img onClick={(e) => {this.resetDraggable()}}></img>
</Draggable>
)
}
}
There happens to be another way! You can use it's exposed ref element to reset its offset. This can be achieved like so:
import React, {useRef, useCallback} from "react";
import Draggable from "react-draggable";
const DragComponent = () => {
// Updates the drag position parameter passed to Draggable
const [dragPosition, setDragPosition] = React.useState(null);
const draggerRef = useRef(null);
// Fires when the user stops dragging the element
const resetDrag = useCallback(() => {
setDragPosition({x: 0, y: 0});
draggerRef.current?.setState({ x: 0, y: 0 }); // This is what resets it!
}, [setDragPosition, draggerRef]);
return (
<Draggable
ref={draggerRef}
onStop={resetDrag}
position={dragPosition}
>
Drag me
</Draggable>
);
};
export default DragComponent;

React - Leaflet MarkerCluster with Popup and Tabs

I am having an issue using the MarkerCluster leaflet component with popup and React-Tabs.
The issue is when I try to reset selected tab inside the popup, it's causing infinite loop This seems to be only when MarkerCluster group is used, otherwise it's working fine for a single marker
My code is as below
custom marker component
const ExtendedMarker = props => {
const initMarker = ref => {
if (ref && props.isOpenMarker) {
ref.leafletElement.openPopup();
}
};
return <Marker ref={initMarker} {...props} />;
};
class CustomMarker extends React.Component {
render() {
const { icon, stop, isDisabledBtn, isOpenMarker, ...props } = this.props
return (
<ExtendedMarker
icon={icon}
position={[stop.latitude, stop.longitude]}
isOpenMarker={isOpenMarker}
>
<Popup minWidth={260} closeButton={true} onOpen={() => this.setState({ tabIndex: 0 })}>
<Tabs selectedIndex={this.state.tabIndex} onSelect={tabIndex => this.setState({ tabIndex })}>
<TabList>
.
.
.
.
index.js
<MarkerClusterGroup showCoverageOnHover={false} maxClusterRadius={50}>
{currentStops.map(stop => (
<CustomMarker
key={v4()}
icon={getCategoryIconMarker(stop.category)}
stop={stop}
{...this.props}
/>
))}
</MarkerClusterGroup>
So this code works fine when MarkerClusterGroup is removed otherwise it's causing an Error: Maximum update depth exceeded
Any help would be appreciated.
Thank You
I think that is an error pattern I encountered when trying to use a function in the following way:
{getCategoryIconMarker(stop.category)}
If you instead use an arrow function, that may improve the situation. At least in my case the error disappeared. So, just replace the function above with:
{() => getCategoryIconMarker(stop.category)}
Hope someone will find it useful.

Polygon fill color not working properly (React Native maps)

I am using Google Maps on iOS and I have Polygons. (react-native-maps)
Before update (to version 0.18.3. - at the moment I am not able to update to latest version) everything works properly, but from now fill color gets weird results.
Sometimes color is ok, sometimes it is not proper, no rules.
On android everything works well.
export const Polygon = (props) => {
return (
<MapView.Polygon
coordinates={ props.selectedAreas }
fillColor={ props.fillColor }
strokeColor={ props.strokeColor }
/>
)
};
Worked for me using the fix from https://github.com/react-native-community/react-native-maps/issues/3025#issuecomment-538345230
import React from 'react';
import { Polygon } from 'react-native-maps';
function CustomPolygon({ onLayout, ...props }) {
const ref = React.useRef();
function onLayoutPolygon() {
if (ref.current) {
ref.current.setNativeProps({ fillColor: props.fillColor });
}
// call onLayout() from the props if you need it
}
return <Polygon ref={ref} onLayout={onLayoutPolygon} {...props} />;
}
export default CustomPolygon;
It is not very pretty but I guess it will have to do until the upstream bug is fixed.

Resources