110 lines
3.8 KiB
JavaScript
110 lines
3.8 KiB
JavaScript
import { useCallback, useEffect, useRef, useState } from "react";
|
|
|
|
const YANDEX_GEOCODER_API_KEY = import.meta.env.VITE_YANDEX_GEOCODER_KEY;
|
|
|
|
export function useYandexMap(enabled, initialCoords = null, initialAddress = "") {
|
|
const mapRef = useRef(null);
|
|
const ymapRef = useRef(null);
|
|
const placemarkRef = useRef(null);
|
|
const initializedRef = useRef(false); // защита от двойного вызова StrictMode
|
|
const requestIdRef = useRef(0); // защита от гонки при быстрых кликах/драгах
|
|
|
|
const [coords, setCoords] = useState(initialCoords);
|
|
const [address, setAddress] = useState(initialAddress ?? "");
|
|
const addressRef = useRef(initialAddress ?? "");
|
|
|
|
const resolveAddress = useCallback((lat, lng) => {
|
|
const requestId = ++requestIdRef.current;
|
|
|
|
fetch(
|
|
`https://geocode-maps.yandex.ru/1.x/?apikey=${YANDEX_GEOCODER_API_KEY}&geocode=${lng},${lat}&format=json&lang=ru_RU`
|
|
)
|
|
.then(r => r.json())
|
|
.then(data => {
|
|
// Игнорируем ответ, если пришёл более новый запрос — иначе
|
|
// устаревший ответ может перезаписать адрес актуальных координат.
|
|
if (requestId !== requestIdRef.current) return;
|
|
|
|
const found =
|
|
data?.response?.GeoObjectCollection?.featureMember?.[0]
|
|
?.GeoObject?.metaDataProperty?.GeocoderMetaData?.text;
|
|
const resolved = found || `${lat.toFixed(5)}, ${lng.toFixed(5)}`;
|
|
setAddress(resolved);
|
|
addressRef.current = resolved;
|
|
})
|
|
.catch(() => {
|
|
if (requestId !== requestIdRef.current) return;
|
|
|
|
const fallback = `${lat.toFixed(5)}, ${lng.toFixed(5)}`;
|
|
setAddress(fallback);
|
|
addressRef.current = fallback;
|
|
});
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
if (!enabled || !mapRef.current) return;
|
|
if (initializedRef.current) return; // уже инициализировали
|
|
initializedRef.current = true;
|
|
|
|
const init = () => {
|
|
if (ymapRef.current) return;
|
|
|
|
const center = initialCoords
|
|
? [initialCoords.latitude, initialCoords.longitude]
|
|
: [55.751574, 37.573856];
|
|
|
|
const map = new window.ymaps.Map(mapRef.current, {
|
|
center,
|
|
zoom: 14,
|
|
controls: ["zoomControl", "geolocationControl"],
|
|
});
|
|
ymapRef.current = map;
|
|
|
|
if (initialCoords) {
|
|
const pm = new window.ymaps.Placemark(center, {}, { draggable: true });
|
|
map.geoObjects.add(pm);
|
|
placemarkRef.current = pm;
|
|
resolveAddress(initialCoords.latitude, initialCoords.longitude);
|
|
|
|
pm.events.add("dragend", () => {
|
|
const [lat, lng] = pm.geometry.getCoordinates();
|
|
setCoords({ latitude: lat, longitude: lng });
|
|
resolveAddress(lat, lng);
|
|
});
|
|
}
|
|
|
|
map.events.add("click", e => {
|
|
const [lat, lng] = e.get("coords");
|
|
setCoords({ latitude: lat, longitude: lng });
|
|
resolveAddress(lat, lng);
|
|
|
|
if (placemarkRef.current) {
|
|
placemarkRef.current.geometry.setCoordinates([lat, lng]);
|
|
} else {
|
|
const pm = new window.ymaps.Placemark([lat, lng], {}, { draggable: true });
|
|
map.geoObjects.add(pm);
|
|
placemarkRef.current = pm;
|
|
|
|
pm.events.add("dragend", () => {
|
|
const [la, ln] = pm.geometry.getCoordinates();
|
|
setCoords({ latitude: la, longitude: ln });
|
|
resolveAddress(la, ln);
|
|
});
|
|
}
|
|
});
|
|
};
|
|
|
|
if (window.ymaps?.ready) {
|
|
window.ymaps.ready(init);
|
|
}
|
|
|
|
return () => {
|
|
ymapRef.current?.destroy();
|
|
ymapRef.current = null;
|
|
placemarkRef.current = null;
|
|
initializedRef.current = false;
|
|
};
|
|
}, [enabled, initialCoords, resolveAddress]);
|
|
|
|
return { mapRef, coords, address, addressRef };
|
|
} |