5.5 KiB
5.5 KiB
To AI Agent: Fix Infinite Re-render Loop and Flickering "ĐANG TÌM ĐƯỜNG TỐI ƯU..." Label
1. Context & Layout Bug Analysis
We are resolving a critical performance and UI bug inside the Map routing engine interface (image.png):
- The Issue: The floating loading indicator badge reading
"ĐANG TÌM ĐƯỜNG TỐI ƯU..."keeps flashing, flickering, or appearing and disappearing in an infinite execution loop. - Root Cause: This is caused by a broken React lifecycle loop. Every time the map triggers a directions route query, it toggles a loading state variable (
isSearching: true). Once the route loads, the state updates (isSearching: false), forcing a component re-render. If the coordinates object (origin,destination) or the map instance ref inside theuseEffectdependency array changes its reference pointer on every render, the hook fires again, creating an endless loop of API fetching and component flashing.
2. Technical Execution Strategy
To terminate this flickering cycle, we must enforce a strict guard rail on the network/calculation trigger pipeline:
- Coordinate Reference Stability: Deconstruct the input latitude/longitude objects into raw primitive string values (e.g.,
origin.lat,origin.lng) inside the hook dependency array to avoid object reference mutations triggering re-renders. - Locking Ref Mechanism: Implement a mutable React tracking reference (
const queryInProgress = useRef(false)) to lock the execution window. If a fetch operation is active, block subsequent duplicate queries from firing. - Clean Loading State Termination: Turn off the search state flag explicitly only after the route geometry polyline has completely finished rendering onto the map viewport layout.
3. Code Refactoring Blueprint
Locate your map navigation layer or modal component (e.g., LocationNavigationModal.tsx) and refactor the execution lifecycle loop as defined below:
import React, { useEffect, useState, useRef } from 'react';
// Inside your Map Navigation Modal / Layer component wrapper:
export const LocationNavigationModal = ({ isOpen, routeData }) => {
const [isSearchingRoute, setIsSearchingRoute] = useState(false);
const mapInstanceRef = useRef<any>(null);
// CRITICAL FIX 2: Guard mechanism to block concurrent duplicated queries
const fetchLockRef = useRef(false);
// Deconstruct coordinate primitives to secure a stable dependency array
const originLat = routeData?.origin?.lat;
const originLng = routeData?.origin?.lng;
const destLat = routeData?.destination?.lat;
const destLng = routeData?.destination?.lng;
useEffect(() => {
if (!isOpen || !originLat || !originLng || !destLat || !destLng) return;
// If an operation is already locked and active, bail out immediately to prevent loops
if (fetchLockRef.current) return;
const calculateOptimalRoute = async () => {
try {
// 1. Activate loading feedback banner
setIsSearchingRoute(true);
fetchLockRef.current = true; // Engage execution lock
console.log("Fetching route coordinates exactly once...");
// --- YOUR MAP COMPONENT ROUTING LOGIC START ---
// Example: const response = await directionsService.route({...});
// await mapInstanceRef.current.drawPolyline(response);
// --- YOUR MAP COMPONENT ROUTING LOGIC END ---
} catch (error) {
console.error("Failed to compile route optimization maps:", error);
} finally {
// 2. Safe, definitive termination of the tracking states
setIsSearchingRoute(false);
fetchLockRef.current = false; // Disengage execution lock
}
};
calculateOptimalRoute();
// Cleanup phase: Reset execution parameters when inputs dismantle or modal closes
return () => {
fetchLockRef.current = false;
setIsSearchingRoute(false);
};
/* CRITICAL FIX 1: Explicitly tracking primitives only.
Do NOT pass full 'routeData', 'mapInstanceRef' or object literals here!
*/
}, [isOpen, originLat, originLng, destLat, destLng]);
return (
<div className="relative w-full h-full">
{/* Map Content Target Canvas */}
<div id="navigation-viewport-map-canvas" className="w-full h-full" />
{/* RENDER CONTROLLER: Only mount the label if routing calculations are actively processing */}
{isSearchingRoute && (
<div className="absolute top-16 left-4 z-30 bg-white/95 dark:bg-slate-900/95 border border-slate-200 dark:border-slate-800 px-3 py-1.5 rounded-full shadow-lg flex items-center gap-2 animate-pulse">
{/* Circular Loading Spinner Element */}
<div className="w-3.5 h-3.5 border-2 border-blue-500 border-t-transparent rounded-full animate-spin" />
<span className="text-[11px] font-bold text-slate-700 dark:text-slate-200 uppercase tracking-wider">
ĐANG TÌM ĐƯỜNG TỐI ƯU...
</span>
</div>
)}
</div>
);
};
## 4. Verification & Quality Acceptance Criteria
[ ] Single Instance Trigger: Check console telemetry outputs. When the map modal mounts, the route compilation routine must print its trace log exactly once.
[ ] Flicker Nullification: The "ĐANG TÌM ĐƯỜNG TỐI ƯU..." badge must display smoothly with an animation pulse. It must not shake, blink, flash, or rapid-cycle on and off.
[ ] Deterministic Hiding: As soon as the blue route line draws completely across the map terrain grid layout, the loading badge must cleanly unmount and disappear from view without reappearing unless a new destination node button is clicked.