0

I have the following issue:

I would like to create a simple choropleth map using react-leaflet's GeoJSON component. I also want to have a tooltip that shows a value on hover over a GeoJSON feature. The problem is performance. A CodePen example can be found here: https://codepen.io/timester-the-typescripter/pen/gOXGKOY

I attach event handlers to each GeoJSON feature and set a state variable in my "main" component that holds the value for the currently hovered area.

const [selected, setSelected] = React.useState(null);

My tooltip relies on that state variable to show its value. Because every mouse hover event causes main component state change, the GeoJSON component gets re-rendered all the time. This is not a huge problem in the CodePen example, but for a full world map it is unfortunately.

I assume that the state change causes this because if I comment out the 2 setSelected lines (line 55, and line 67 in CodePen), the re-renders (calls to createGeoJSON ) stop, and the event handlers run a lot faster as the profiler pictures show below.

Mouseout event with state change: Mouseout event with state change

Mouseout event with state change commented out: Mouseout event with state change commented out

I tried many solutions with no success. For example I think I can't memoize the GeoJSON component because the Tooltip must be its child and that depends on the state of the main component.

In the future I want to add more components depending on the hovered state variable like a legend, etc.. and maybe that variable will be a bit more complex than a simple number too.

What options do I have here? I had another stackowerflow question about this, but then I did not understand the problem completely, so it was not super focused. I'm at the point where I'm thinking about rewriting it in Angular. I found react and react-leaflet very pleasant to work with, until this issue came up.

2 Answers 2

2

The problem is that currently you are recreating the map with the appropriate Tooltip on each mouseover via the state. Instead, you should bind all Tooltips at map creation with layer.bindTooltip(). This will allow you to just show/hide them without having to recreate the map again, since they are already created and their creation will not rely on state.

See this github issue for an example with Popups (but the logic for Tooltips should be the same): https://github.com/PaulLeCam/react-leaflet/issues/33

1

I got help from this reddit comment https://www.reddit.com/r/reactjs/comments/std46f/comment/hx3yq34/?utm_source=share&utm_medium=web2x&context=3

The solution I applied based on this is not to use the Tooltip as a child component, but bind a tooltip to each layer in the onEachFeature method.

const onEachFeature = useCallback((feature, layer) => {
    layer.bindTooltip(feature.properties.COUNT, {sticky: true});
    ...
    

This way I could also wrap the GeoJSON component in a useMemo() as there were no longer dependencies on the selected state. I also wrapped the style and onEachFeature functions in useCallback().

This combination solved the issue!

Not the answer you're looking for? Browse other questions tagged or ask your own question.