Resolving Map flickering in a React Application: Enhancing User Experience
In this article, we explore a common issue encountered in a React application that integrates a Map component for location-based item searches.
The primary challenge was a “blinking” or “flickering” effect on the map whenever users adjusted the zoom level or changed the map’s position. This visual disturbance significantly impacted the user experience.
The solution was found in restructuring the map component’s hierarchy within the application. This strategic change effectively eliminated the flickering, resulting in a smoother and more visually appealing user interaction.
The article details this problem-solving journey, highlighting the importance of component organization in React development and offering insights for developers facing similar issues.
Below I use jsx pseudo code to demonstrate the problem and the fix
The wrong way to do it:
App.js:
import "./styles.css";
import Main from './Main'
export default function App() {
return (
<div className="App">
<Main />
</div>
);
}Main.js:
import "../styles/_main.scss";
import React from "react";
import MapView from "./MapView";
import MapMarkers from "./MapMarkers";
function Main(props) {
return (
<div>
{isShowMapView && (
<MapView>
<MapMarkers videos={videos} />
</MapView>
)}
...
...
...
<div
className={ "videos-view"} >
<VideoList />
</div>
</div>
);
}
export default Main;MapView.js:
import "../styles/_mapView.scss";
import {GoogleMap, useJsApiLoader} from "@react-google-maps/api";
import { useContext, useRef, useCallback, useMemo } from "react";
import { mapContainerStyle, mapStylers } from "../styles/map-view-styles";
import UsersContext from "../context/users-context";
import VideoContext from '../context/video-context';
import { useAuth } from "../context/authProvider";
import { fetchVideosByBounds } from "../services/map-service";
import MapMarkers from "./MapMarkers";
export default function MapView({ children }) {
const { timestamp, mapApiKey} = useContext(UsersContext);
const { dispatch, filterTags, center, setCenter, zoom, setZoom } = useContext(VideoContext);
const auth = useAuth();
const mapRef = useRef(null);
const { isLoaded } = useJsApiLoader({
id: "google-map-script",
googleMapsApiKey: mapApiKey,
});
const onLoad = useCallback((map) => {
mapRef.current = map
}, []);
const onUnmount = useCallback((map) => (mapRef.current = null), []);
const boundsChanged = () => {}
const fetchVideosOnChange = async ( northeast, southwest) => {
const map = mapRef.current;
if (map) {
const mapBounds = map.getBounds({asGeoJSON: true});
if (mapBounds) {
const northEast = mapBounds.getNorthEast().toJSON();
const southWest = mapBounds.getSouthWest().toJSON();
try {
const response = await fetchVideosByBounds(
auth.userToken,
timestamp,
filterTags,
[northEast, southWest]
);
if (response.body) {
////*****/////
dispatch({
type: "POPULATES_VIDEOS", ////this function is changing the videos array state///
videos: response.body,
});
}
////*****/////
} catch (error) {
console.log('@@@---> MapView --- error.message: ', error.message);
}
}
}
}
const options = useMemo(() => ({
mapTypeControl: false,
streetViewControl: false,
styles: mapStylers
}), []);
const map = useMemo(() => {
return (
<>
<div className="map-view">
{console.log('@@@---> render mapview')}
{isLoaded ? (
<GoogleMap
mapContainerStyle={mapContainerStyle}
center={center}
zoom={zoom}
mapTypeId="roadmap"
extraMapTypes={undefined}
options={options}
onLoad={onLoad}
onUnmount={onUnmount}
onZoomChanged={fetchVideosOnChange}
onMouseUp={fetchVideosOnChange}
onBoundsChanged={boundsChanged}
>
{children}
</GoogleMap>
) : (
<></>
)}
</div>
</>
)}, [isLoaded]);
return map;
}
Why is this wrong:
Get Tal Shnitzer’s stories in your inbox
Join Medium for free to get updates from this writer.
<Main/> renders <MapView/>
When user changes the zoom of the map it causes <MapView/> to change the “videos” state
The change in “videos” state causes <Main/> to re-render because of the <VideoList /> component that is a child in <Main/>
Re-render of <Main/> causes re-mount of the <MapView/> component, thus a re-load the map inside it — → hence map “Blinks”
The right way to do it:
App.js:
import "./styles.css";
import Main from './Main';
import MapView from "./MapView";
import MapMarkers from "./MapMarkers";
export default function App() {
return (
<div className="App">
{isShowMapView && (
<MapView>
<MapMarkers videos={videos} />
</MapView>
)}
<Main />
</div>
);
}Main.js:
import "../styles/_main.scss";
import React from "react";
function Main(props) {
return (
<div>
...
...
...
<div
className={ "videos-view"} >
<VideoList />
</div>
</div>
);
}
export default Main;MapView.js stays the same.
the fix is to move <Main/> and <MapView/> to be in the same hierarchy as <App/> children.
This way when there is a change of the map bound in <MapView/>, causing “videos” state to change — -> <Main/> re-renders but that does not cause the <MapView/> to remount and the “Blinks” issue solved
Remember, the key takeaway is:
- If the map in your app has flickering upon zoom/area change — check the code hierarchy structure.
If you have any questions or experiences to share, feel free to leave a comment below. I’d love to hear about your own map integration experience.
Thank you for reading, and happy coding!








