I am using react + redux and react-leaflet v3 + an API to load markers to pin on a map dynamically based on panning behavior.
I have it so that "dragend" event is triggered and the map boundaries, zoom level and center are calculated in order to create the appropriate parameters to feed into the API endpoint. This way, as the user pans the map, the markers are fed in dynamically.
The marker data is saved in the redux store and the API call is triggered via useEffect listening on endpoint changes.
The problem is that it seems that all the markers are re-rendered at every pan and this makes the application jittery and slow. I would like it so that only new markers are rendered and the old markers are not re-rendered. Additionally, old markers that are outside the boundary should simply be removed. However, this is just not the case. I thought that as long as the markers have a key, that react-redux would be able to compare the old data with the new and only render the new components.
My marker loading is done via createAyncThunk like so:
export const getCities = createAsyncThunk("markers/getCities", async (endpoint, thunkAPI) => {
try {
const response = await axios.get(endpoint);
return response.data;
} catch (error) {
return thunkAPI.rejectWithValue({ error: error.message });
}
});
with the following slice:
// CREATE SLICE
const markerSlice = createSlice({
name: "markers",
initialState: {
cities: [],
markerType: "cities",
endpoint: "/api/get/cities",
},
reducers: {
// some reducer code
},
extraReducers: (builder) => {
builder.addCase(getCities.pending, (state) => {
state.cities = [];
});
builder.addCase(getCities.fulfilled, (state, { payload }) => {
state.cities = payload;
});
builder.addCase(getCities.rejected,(state, action) => {
state.loading = "error";
});
}
});
and my Map component is like so (simplified for readability):
import React, { useEffect } from "react";
import { MapContainer, Marker, Popup, TileLayer, useMap, useMapEvents } from "react-leaflet";
import "../../css/app.css";
import { useSelector, useDispatch, batch } from "react-redux";
import { getCities, setEndpoint } from "../features/markerSlice";
import { setLatBnd, setLngBnd, setZoom, setLat, setLng } from "../features/mapSlice";
export const LeafMap = () => {
const dispatch = useDispatch();
//marker state (marker data)
const stateMarker = useSelector(state => state.marker);
// map state (center, zoom level, bounds, etc)
const stateMap = useSelector(state => state.map);
// This calls the API to retrieve data
useEffect(() => {
dispatch(getCities(stateMarker.endpoint));
}, [stateMarker.endpoint]);
// Custom Marker Component to render markers
const GetMarkers = () => {
const markerType = stateMarker.markerType;
return stateMarker.cities.map((el, i) => (
<Marker
key={i}
position={[el.latitude, el.longitude]}
>
</Marker>
));
};
// This is a child component to MapContainer (leaflet v3) which listens to map events
function GetMapProperties() {
const map = useMap();
// listen to drag event end
useMapEvents({
dragend: () => {
// Get map info in order to create endpoint parameters for API call
const bounds = map.getBounds();
const latBnd = bounds['_northEast'].lat
const lngBnd = bounds['_northEast'].lng
const zoom = map.getZoom();
const lat = map.getCenter().lat;
const lng = map.getCenter().lng;
// update endpoint which triggers API call (via useEffect on top)
dispatch(setEndpoint({type:"trees", lat:lat, lng:lng, latbnd:latBnd, lngbnd:lngBnd}))
},
});
// render component based on if there is available data
if (stateMarker.cities.length > 0) {
return (
<MapContainer preferCanvas={true} center={[stateMap.lat, stateMap.lng]} zoom={stateMap.zoom} scrollWheelZoom={true}>
<GetMapProperties />
<TileLayer
attribution='© <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
/>
<GetMarkers />
</MapContainer>
);
} else {
return (
<MapContainer center={[stateMap.lat, stateMap.lng]} zoom={stateMap.zoom} scrollWheelZoom={true}>
<TileLayer
attribution='© <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
/>
</MapContainer>
);
}
}