自定义Hook执行异常:useEffect传空数组无返回值,移除则无限循环
Fixing Your useCurrentLocation Hook: Empty Dependency Array Issues & Infinite Loops
Let's walk through what's causing your problems and fix the hook properly.
First, Why You're Seeing These Issues
- Infinite loops without a dependency array: Without specifying dependencies,
useEffectruns every time your component re-renders. Since yourfetchLocationfunction callssetLocationorsetError, those state updates trigger a re-render, which kicks offuseEffectagain—creating an infinite cycle. - No location with an empty dependency array: While the empty array should run the effect only once on mount, your current error handling has redundant logic, and you aren't accounting for potential changes to the
optionsparameter. Additionally, missing a check for component mount status can lead to state updates being ignored if the geolocation call finishes after the component unmounts.
Here's the Fixed Hook Code
type GeoOptions = PositionOptions; type GeoPosition = GeolocationPosition; type GeoError = GeolocationPositionError; const useCurrentLocation = (options: GeoOptions | null) => { const [location, setLocation] = useState<GeoPosition | null>(null); const [error, setError] = useState<GeoError | null>(null); useEffect(() => { let isMounted = true; // Track if component is still active to avoid stale state updates const fetchLocation = async () => { // First check if browser supports geolocation if (!navigator.geolocation) { if (isMounted) { setError({ code: 0, message: "Geolocation is not supported by your browser", } as GeoError); } return; } try { const position = await new Promise<GeoPosition>((resolve, reject) => { navigator.geolocation.getCurrentPosition( resolve, reject, options ?? { enableHighAccuracy: true, timeout: 15000, maximumAge: 10000, } ); }); if (isMounted) { setLocation(position); setError(null); // Clear any previous errors when we get a valid location } } catch (err) { if (isMounted) { setLocation(null); setError(err as GeoError); } } }; fetchLocation(); // Cleanup: Mark component as unmounted to prevent stale state updates return () => { isMounted = false; }; }, [options]); // Add options as a dependency to react to configuration changes return { location, error }; };
Key Fixes Explained
- Dependency array with
options: Now the effect only runs whenoptionschanges (or on initial mount), stopping infinite loops while still reacting to configuration updates. - Mounted flag cleanup: The
isMountedvariable ensures we don't try to update state after the component has unmounted, which avoids console warnings and ensures state changes only apply when the component is active. - Simplified error handling: We moved all state updates into the try/catch block, removed redundant checks (like
if (loc)sincegetCurrentPosition's success callback always passes a valid position), and added a check for browser geolocation support. - Cleaner default option handling: Used
??(nullish coalescing) instead of a ternary for more readable default option logic.
How to Use It
Call the hook with your desired options (or null to use defaults):
const { location, error } = useCurrentLocation(null); if (error) { return <div>Error: {error.message}</div>; } if (!location) { return <div>Loading location...</div>; } return ( <div> Latitude: {location.coords.latitude}<br /> Longitude: {location.coords.longitude} </div> );
内容的提问来源于stack exchange,提问作者tvankith
相关产品推荐
相关产品推荐

