showing 1000 tooltip is lagging my react leaflet , what can i do? - reactjs

const map = useMap();
useMapEvents({
zoomend: () => {
setZoomLevel(map._zoom);
},
});
<GeoJSON
key={surrounding?.id}
data={JSON.parse(surrounding.st_asgeojson)}
style={() => ({
color: 'black',
weight: 0.5,
fillColor: '#FF4500',
fillOpacity: 1,
})}
className={classes.polygon}
eventHandlers={{
click: (event) => onEachFeature(event, surrounding),
}}
>
{zoomLevel > 17
&& (
<>
<Tooltip className="leaflet-tooltip-own" permanent>
{surrounding?.name_bati}
</Tooltip>
</>
)}
</GeoJSON>
when i remove permanent the performance are is better but i have to show all the tooltips at the same time what can i do ?

Related

How can I get my Modal in Leaflet to appear in front of the map?

I am using leaflet to display a map of Jamaica which should prompt a modal to pop up once the user clicks on one of the parishes (states). Currently the modal appears behind the map once a parish is clicked.
Below I have placed the react code.
const center = [18.19368269899244, -77.39527725784035];
export default function App() {
const [show, setShow] = useState(false)
return (
<MapContainer
center={center}
zoom={9}
style={{ width: '100vw', height: '100vh' }}
>
<TileLayer
url="https://api.maptiler.com/maps/basic/256/{z}/{x}/{y}.png?key=73p7aIRQ0vUQYQlwBn1Q"
attribution='© MapTiler © OpenStreetMap contributors'
/>
{
parishesData.features.map((parish) => {
const coordinates = parish.geometry.coordinates[0].map((item) => [item[1], item[0]]);
console.log(parish.properties.name, coordinates)
const color = (d) => {
return d > 1000 ? '#800026' :
d > 500 ? '#BD0026' :
d > 200 ? '#E31A1C' :
d > 100 ? '#FC4E2A' :
d > 50 ? '#FD8D3C' :
d > 20 ? '#FEB24C' :
d > 10 ? '#FED976' :
'#FFEDA0';
}
return (<Polygon
pathOptions={{
fillColor: color(parish.properties.density),
fillOpacity: 0.7,
weight: 2,
opacity: 1,
dashArray: 3,
color: 'white'
}}
positions={coordinates}
eventHandlers={{
mouseover: (e) => {
const layer = e.target;
layer.setStyle({
dashArray: "",
fillColor: "#BD0026",
fillOpacity: 0.7,
weight: 2,
opacity: 1,
color: "white",
})
},
mouseout: (e) => {
const layer = e.target;
layer.setStyle({
fillOpacity: 0.7,
weight: 2,
dashArray: "3",
color: 'white',
fillColor: color(parish.properties.density),
});
},
click: (e) => {
const layer = e.target;
setShow(true);
console.log("zoom to", parish.properties.name);
}
}}
/>)
})
}
<div className='login-modal'>
<button onClick={()=>{
console.log("button pressed")
setShow(true)
}}>Show Modal</button>
<Modal show={show} title ="Logins" onClose={()=> setShow(false)}>
<div className='modal-body'>Logins go here</div>
<div className='modal-body'>Logins go here</div>
</Modal>
</div>
</MapContainer>
);
}
I have tried changing the z-index of the MapContainer and the login modal div but the modal still appeared behind the map. Additionally I have a button that is appearing at the top left of the screen behind the map. How do I get these elements to show on top of the map where I have it zoomed in?

MUI anchorEl prop provided is invalid

I am trying to code a mobile menu, where when the user clicks on the menu icon, the menu is displayed.
I have the following code:
const StyledMenu = withStyles({
paper: {
border: '1px solid #d3d4d5',
},
})((props, ref) => (
<Menu
elevation={0}
getContentAnchorEl={null}
anchorOrigin={{
vertical: 'bottom',
horizontal: 'center',
}}
transformOrigin={{
vertical: 'top',
horizontal: 'center',
}}
{...props}
/>
))`
and the component:
export default function Header(props) {
const [state, setState] = useState({ mobileView: true, drawerOpen: false, anchorEl: null, openItem: null })
const { mobileView, drawerOpen, anchorEl } = state
const classes = useStyles()
useEffect(() => {
const setResponsiveness = () => {
return window.innerWidth < 900
? setState((prevState) => ({ ...prevState, mobileView: true }))
: setState((prevState) => ({ ...prevState, mobileView: false }));
};
setResponsiveness();
window.addEventListener("resize", () => setResponsiveness());
return () => {
window.removeEventListener("resize", () => setResponsiveness());
}
}, []);
const MobileMenu = (props) => {
const handleClick = (id) => {
if (state.openItem === id) {
setState((prevState) => ({ ...prevState, openItem: null }))
} else {
setState((prevState) => ({ ...prevState, openItem: id }))
}
}
const handleDrawerOpen = (event) =>
setState((prevState) => ({ ...prevState, drawerOpen: true, anchorEl: event.currentTarget }));
const handleDrawerClose = () =>
setState((prevState) => ({ ...prevState, drawerOpen: false }));
return (
<Toolbar>
<IconButton
{...{
edge: "start",
color: "inherit",
"aria-label": "menu",
"aria-haspopup": "true",
variant: "contained",
onClick: handleDrawerOpen,
}}
>
<MenuIcon className={classes.menuIcon} fontSize='40px' />
</IconButton>
<StyledMenu
{...{
id: 'custom-menu',
anchor: "left",
open: drawerOpen,
onClose: handleDrawerClose,
anchorEl: anchorEl
}}
>
<List component='nav' className={classes.appMenu} disablePadding>
{menuItems.map((item, index) => {
const hasSubMenus = item.hasOwnProperty('subMenus')
const ListItemLink = () => {
return (
<ListItem key={index} button component={Link} to={item.link} className={classes.menuItem}>
<ListItemText>{item.label}</ListItemText>
</ListItem>
)
}
const ListItemToggle = () => (
<ListItem key={index} button onClick={() => handleClick(index)} className={classes.menuItem}>
<ListItemText>{item.label}</ListItemText>
{index === state.openItem ? <IconExpandLess /> : <IconExpandMore />}
</ListItem>
)
return (
<React.Fragment key={index}>
{!hasSubMenus ? <ListItemLink /> :
<>
<ListItemToggle />
<Collapse in={index === state.openItem} timeout='auto' className={classes.collapseWrapper}>
<div className={classes.collapseDiv}>
<Divider />
<List component='div' disablePadding>
{item.subMenus.map((el, key) => (
<ListItem key={key} button component={Link} to={el.link} className={classes.menuItem}>
<ListItemText inset>{el.label}</ListItemText>
</ListItem>
))}
</List>
</div>
</Collapse>
</>
}
</React.Fragment>
)
})}
</List>
</StyledMenu>
<Logo />
</Toolbar>
)
}
When the clicking on the button, I get the following error:
React does not recognize the getContentAnchorEl prop on a DOM element.
MUI: The anchorEl prop provided to the component is invalid.
The anchor element should be part of the document layout.
Make sure the element is present in the document or that it's not display none.
I solved the problem by moving the anchorEl state from parent (Header) component state to local (MobileMenu) component.
I had the same error message and took the hint, "Make sure the element is present in the document or that it's not display none."
First I was looking on SO when I found this question. I've a similar, but different use case with the #mui/system to show/hide different navigation using <Box sx={{. I'm sharing the way I got rid of the same error you report. I was using display: none, so that in changing from lg to md the anchorEl was nested within a parent element that was indeed set to display none (like the error message suggests).
To get rid of display: none I used visibility along with width to collapse like so:
return (
<List id="id-nav" component="nav" className={classes.root}>
<Box sx={{
display: {
lg: 'inherit'
},
visibility: {
sm: 'hidden',
md: 'hidden',
lg: 'visible'
},
width: {
sm: 0,
md: 0,
lg: 'auto'
},
}}
>
{largeNav}
</Box>
<Box sx={{
display: {
sm: 'inherit',
md: 'inherit'
},
visibility: {
sm: 'visible',
md: 'visible',
lg: 'hidden'
},
width: {
sm: 'auto',
md: 'auto',
lg: 0
}
}}
>
{mediumNav}
</Box>
</List>
);
For future people, if you're wrapping your AppBar in a HideOnScroll/Slide component, that can be the source of the issue (and it was for me). Getting rid of that makes the anchorEl work as expected.
See related question/solution here: Material Ui popover is not on right position

How to set ComposableMap not-focusable?

I'm trying to make a map that will show the name of the country when you hover your cursor. For this I am using react-simple-map with react-tooltip:
function OnHoverMap() {
const [content, setContent] = useState("");
return (
<div>
<Map setTooltipContent={setContent} />
<ReactTooltip>{content}</ReactTooltip>
</div>
)
}
const Map = ({ setTooltipContent }) => {
return (
<>
<ComposableMap data-tip="" projectionConfig={{ scale: 200 }}>
<ZoomableGroup>
<Geographies geography={geoUrl}>
{({ geographies }) =>
geographies.map(geo => (
<Geography
key={geo.rsmKey}
geography={geo}
onMouseEnter={() => {
const { NAME } = geo.properties;
setTooltipContent(`${NAME}`);
}}
onMouseLeave={() => {
setTooltipContent("");
}}
style={{
default: {
fill: "#D6D6DA",
outline: "none"
},
hover: {
fill: "#F53",
outline: "none"
},
pressed: {
fill: "#E42",
outline: "none"
}
}}
/>
))
}
</Geographies>
</ZoomableGroup>
</ComposableMap>
</>
);
};
This works fine, but there is a problem when zooming and moving the map - the focus shifts to the overall map (ComposableMap), and the panel with country name appear above the map, not over the country:
On react-simple-maps website mentions, what is not possible to set data-tip on Geography directly.
Is there a way to solve this problem? Does it help if I make ComposableMap non-focusable?
Instead of adding "data-tip" to ComposableMap, add it to the outer div which is holding your Map and ReactTooltip.

ClickAwayListener component not working with DragDropContext

I made a dropdown using Button and a Popper using Material UI components where you can click on the button and get a list of subjects to choose from. To make the popper disappear either we can click on the button again or use a <ClickAwayListener> component which listens to click event and closes the Popper. Now I've to make the list capable of drag and drop feature so I use the react-beautiful-dnd npm package.But the <ClickAwayListener> doesn't seem to work when I include <DragDropContext>, <Droppable> and <Draggable> components.Can anyone help me figure it out?
Here's the code without drag and drop feature. CodeSandbox link https://codesandbox.io/s/gallant-newton-mfmhd?file=/demo.js
const subjectsFromBackend = [
{ name: "Physics", selected: false },
{ name: "Chemistry", selected: false },
{ name: "Biology", selected: false },
{ name: "Mathematics", selected: false },
{ name: "Computer Science", selected: false },
];
const useStyles = makeStyles((theme) => ({
root: {
display: "flex"
},
paper: {
marginRight: theme.spacing(2)
}
}));
export default function MenuListComposition() {
const classes = useStyles();
const [open, setOpen] = React.useState(false);
const anchorRef = React.useRef(null);
const [subjects, setSubjects] = React.useState(subjectsFromBackend);
const handleToggle = () => {
setOpen(!open);
};
const handleClose = () => {
setOpen(false);
};
const ColumnItem = ({ subjectName, selected }) => {
return (
<>
<Grid container>
<Grid item>
<Checkbox checked={selected} />
</Grid>
<Grid item>{subjectName}</Grid>
</Grid>
</>
);
};
return (
<div className={classes.root}>
<div>
<Button
ref={anchorRef}
onClick={handleToggle}
style={{ width: 385, justifyContent: "left", textTransform: "none" }}
>
{`Subjects Selected`}
</Button>
<Popper
open={open}
anchorEl={anchorRef.current}
role={undefined}
transition
disablePortal
>
{({ TransitionProps, placement }) => (
<Grow
{...TransitionProps}
style={{
transformOrigin:
placement === "bottom" ? "center top" : "center bottom"
}}
>
<Paper style={{ maxHeight: 200, overflow: "auto", width: 385 }}>
<ClickAwayListener onClickAway={handleClose}>
<List>
{subjects.map((col, index) => (
<ListItem>
<ColumnItem
subjectName={col.name}
selected={col.selected}
/>
</ListItem>
))}
</List>
</ClickAwayListener>
</Paper>
</Grow>
)}
</Popper>
</div>
</div>
);
}
Here's the same code using drag and drop. CodeSandbox Link https://codesandbox.io/s/material-demo-forked-ertti
const subjectsFromBackend = [
{ name: "Physics", selected: false },
{ name: "Chemistry", selected: false },
{ name: "Biology", selected: false },
{ name: "Mathematics", selected: false },
{ name: "Computer Science", selected: false },
];
const useStyles = makeStyles((theme) => ({
root: {
display: "flex"
},
paper: {
marginRight: theme.spacing(2)
}
}));
export default function MenuListComposition() {
const classes = useStyles();
const [open, setOpen] = React.useState(false);
const anchorRef = React.useRef(null);
const [subjects, setSubjects] = React.useState(subjectsFromBackend);
const handleToggle = () => {
setOpen(!open);
};
const handleClose = () => {
setOpen(false);
};
const ColumnItem = ({ subjectName, selected }) => {
return (
<>
<Grid container>
<Grid item>
<Checkbox checked={selected} />
</Grid>
<Grid item>{subjectName}</Grid>
</Grid>
</>
);
};
const onDragEnd = (result, subjects, setSubjects) => {
const { source, destination } = result;
if (!destination) return;
if (source.droppableId !== destination.droppableId) return;
const copiedItems = [...subjects];
const [removed] = copiedItems.splice(source.index, 1);
copiedItems.splice(destination.index, 0, removed);
setSubjects(copiedItems);
};
return (
<div className={classes.root}>
<div>
<Button
ref={anchorRef}
onClick={handleToggle}
style={{ width: 385, justifyContent: "left", textTransform: "none" }}
>
{`Subjects Selected`}
</Button>
<Popper
open={open}
anchorEl={anchorRef.current}
role={undefined}
transition
disablePortal
>
{({ TransitionProps, placement }) => (
<Grow
{...TransitionProps}
style={{
transformOrigin:
placement === "bottom" ? "center top" : "center bottom"
}}
>
<DragDropContext
onDragEnd={(res) => onDragEnd(res, subjects, setSubjects)}
>
<Paper style={{ maxHeight: 200, overflow: "auto", width: 385 }}>
<ClickAwayListener onClickAway={handleClose}>
<Droppable droppableId={"subjectsColumn"}>
{(provided, snapshot) => (
<div
{...provided.droppableProps}
ref={provided.innerRef}
>
<List>
{subjects.map((col, index) => (
<Draggable
key={col.name}
draggableId={col.name}
index={index}
>
{(provided, snapshot) => (
<div
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}
>
<ListItem>
<ColumnItem
subjectName={col.name}
selected={col.selected}
/>
</ListItem>
</div>
)}
</Draggable>
))}
</List>
{provided.placeholder}
</div>
)}
</Droppable>
</ClickAwayListener>
</Paper>
</DragDropContext>
</Grow>
)}
</Popper>
</div>
</div>
);
}
I found out that the ClickAwayListener's child component should be wrapped around a div so that the click event could be triggered properly.
You need to have your ClickAwayListener at the top when you are using the Drag and Drop.
return (
<ClickAwayListener
onClickAway={() => {
console.log("i got called");
handleClose();
}}
>
.....
</ClickAwayListener>
Here is the working codesandbox - https://codesandbox.io/s/material-demo-forked-h1o1s?file=/demo.js:4877-4897

How do you transition framer motion values?

I have a motion value that is updated whenever a component is being hovered. I thought that framer-motion automatically took care of animating the value to its new state but apparently it doesn't. How can I transition the new values of my motion value? It is also important to note that I'm aware of the whileHover prop that exists but I don't want to use it here.
This example component illustrates my situation:
const Component = () => {
const scale = useMotionValue(defaultScale);
return (
<motion.img
style={{ scale }}
onMouseEnter={ () => scale.set(1.35) }
onMouseLeave={ () => scale.set(defaultScale) }
/>
)
}
Have you tried useSpring?
const Component = () => {
const scale = useSpring(1);
return (
<motion.div
style={{ scale, background: "tomato", width: "100px", height: "100px" }}
onMouseEnter={() => scale.set(1.35)}
onMouseLeave={() => scale.set(1)}
/>
);
};
Docs: https://www.framer.com/api/motion/motionvalue/#usespring
Or you can also use useAnimation to create custom controls and transitions:
const Component2 = () => {
const controls = useAnimation();
return (
<motion.div
style={{ background: "blue", width: "100px", height: "100px" }}
animate={controls}
onMouseEnter={() => controls.start({ scale: 1.35 })}
onMouseLeave={() => controls.start({ scale: 1 })}
/>
);
};
Docs: https://www.framer.com/api/motion/animation/#animation-controls
Codesandbox with both examples: https://codesandbox.io/s/httpsstackoverflowcomquestions64077992-j63qv?file=/src/App.js

Resources