Яндекс карта в модальном окне в приложении Next
Пишу на Next.js и использую яндекс карты 2.1. У меня есть модальное окно с яндекс картой внутри, при клике на карту должна создаваться точка в месте клика но создается в другом месте карты, получается что координаты получаю не того места куда сделал клик. Заметил что если использовать тот же код в React.js он работает корректно. И если карту отображать просто на странице то тоже работает корректно. В чем может быть причина и как ее решить?
<Script src="https://api-maps.yandex.ru/2.1/?apikey=*****&suggest_apikey=********3&lang=ru_RU" strategy="beforeInteractive" />
"use client";
import { useState } from "react";
import { useRouter } from "next/navigation";
import dynamic from "next/dynamic";
import Providers from "../components/Providers/Providers";
import AuthWrapper from "../components/AuthWrapper/AuthWrapper";
import Loader from "../components/Loader/Loader";
import Carousel from "../components/Carousel/Carousel";
import Opportunities from "../components/Opportunities/Opportunities";
import SearchEngines from "../components/SearchEngines/SearchEngines";
import Landlords from "../components/Landlords/Landlords";
import InfrastructureCompanies from "../components/InfrastructureCompanies/InfrastructureCompanies";
import TelecomOperators from "../components/TelecomOperators/TelecomOperators";
import Footer from "../components/Footer/Footer";
import ButtonHome from "../components/ButtonHome/ButtonHome";
import ModalRegistrations from "../components/ModalRegistrations/ModalRegistrations";
import ModalRegistrationsLandlords from "../components/ModalRegistrationsLandlords/ModalRegistrationsLandlords";
import Image from "next/image";
import styles from "./style.module.css";
import { cards } from "./utils";
const DynamicDiv = dynamic(
() => Promise.resolve((props) => <div {...props} />),
{
ssr: false,
loading: () => <Loader />,
}
);
export default function MainLayout() {
const router = useRouter();
const [openModal, setOpenModal] = useState(false);
const [openModalLandlords, setOpenModalLandlords] = useState(false);
return (
<Providers>
<AuthWrapper>
<DynamicDiv className={styles.containeDiv}>
<div className={styles.main}>
<header className={styles.header}>
<div className={styles.conainerHeader}>
<div className={styles.icon}>
<Image src="/images/logo.svg" alt="Логотип" fill />
</div>
<h1 className={styles.title}>TelecomSpot</h1>
</div>
<div className={styles.conainerText}>
<a href="#" className={styles.text}>
О компании
</a>
<a href="#opportunities" className={styles.text}>
Возможности
</a>
<a href="#landlords" className={styles.text}>
Арендодателям
</a>
<a href="#infrastructureCompanies" className={styles.text}>
Инфрастуктуре
</a>
<a href="#telecomOperators" className={styles.text}>
Операторам
</a>
<a href="#searchEngines" className={styles.text}>
Поисковикам
</a>
</div>
<ButtonHome
text={"Личный кабинет"}
onClick={() => router.push("/login")}
/>
</header>
<div className={styles.containerIntro}>
<div className={styles.introContent}>
<div className={styles.introText}>
<h1>
Создаем новый уровень взаимодействия в отрасли сотовой связи
</h1>
<p>
Эффективный поиск площадок для создания качественной
инфраструктуры связи – одна из ключевых задач отрасли
</p>
</div>
</div>
<div className={styles.introImage}>
<img src="/images/home.svg" alt="Картинка" />
</div>
</div>
<div className={styles.statsWrapper}>
<div className={styles.сontainerStats}>
<div className={styles.itemStat}>
<p className={styles.itemNumber}>95 004 +</p>
<p className={styles.itemDescription}>
объектов инфраструктуры
</p>
</div>
<div className={`${styles.itemStat} ${styles.itemStatBorder}`}>
<p className={styles.itemNumber}>3 010 +</p>
<p className={styles.itemDescription}>
площадок арендодателя
</p>
</div>
<div className={styles.itemStat}>
<p className={styles.itemNumber}>6 308 +</p>
<p className={styles.itemDescription}>зон поиска</p>
</div>
</div>
</div>
<div className={styles.cardsContainer}>
{cards.map((item, index) => {
return (
<div key={index} className={styles.card}>
<img
className={styles.cardIcon}
src={item?.icon?.src}
alt="icon"
/>
<div className={styles.cardContainerText}>
<p className={styles.cardTitle}>{item?.title}</p>
<p className={styles.cardText}>{item?.text}</p>
</div>
</div>
);
})}
</div>
<Carousel setOpenModal={setOpenModal} />
<Opportunities />
<SearchEngines setOpenModal={setOpenModal} />
<Landlords setOpenModal={setOpenModalLandlords} />
<InfrastructureCompanies setOpenModal={setOpenModal} />
<TelecomOperators setOpenModal={setOpenModal} />
<Footer />
</div>
{openModal && (
<ModalRegistrations
openModal={openModal}
setOpenModal={setOpenModal}
/>
)}
{openModalLandlords && (
<ModalRegistrationsLandlords
openModal={openModalLandlords}
setOpenModal={setOpenModalLandlords}
/>
)}
</DynamicDiv>
</AuthWrapper>
</Providers>
);
}
"use client";
import { useState, useEffect } from "react";
import { Modal, Form, Input, Button, Select } from "antd";
import YandexMapModal from "../YandexMapModal/YandexMapModal";
import styles from "./style.module.css";
export default function ModalRegistrationsLandlords({
openModal,
setOpenModal,
}) {
const [form] = Form.useForm();
const [legalStatus, setLegalStatus] = useState(null);
const [address, setAddress] = useState("");
const [coordinates, setCoordinates] = useState(null);
const [mapKey, setMapKey] = useState(0); // Ключ для перемонтирования карты
const handleLegalStatusChange = (value) => {
setLegalStatus(value);
};
const handleAddressFromMap = (addr) => {
setAddress(addr);
form.setFieldsValue({ address: addr });
};
const handleCoordinatesFromMap = (coords) => {
setCoordinates(coords);
};
const handleAddressChange = async (e) => {
const addr = e.target.value;
setAddress(addr);
if (window.ymaps && addr) {
try {
const res = await window.ymaps.geocode(addr);
const firstGeoObject = res.geoObjects.get(0);
if (firstGeoObject) {
const coords = firstGeoObject.geometry.getCoordinates();
handleCoordinatesFromMap(coords);
}
} catch (error) {
console.error("Geocoding failed:", error);
}
}
};
const handleSubmit = async () => {
try {
const values = await form.validateFields();
console.log("Form values:", values);
console.log("Coordinates:", coordinates);
setOpenModal(false);
} catch (error) {
console.error("Validation failed:", error);
}
};
// Перемонтируем карту при открытии модального окна
useEffect(() => {
if (openModal) {
setMapKey((prev) => prev + 1);
}
}, [openModal]);
return (
<Modal
title={
<div className={styles.titleContainer}>
<h3 className={styles.title}>Форма регистрации</h3>
<p className={styles.text}>для арендодателей</p>
</div>
}
open={openModal}
onCancel={() => setOpenModal(false)}
footer={
<div className={styles.submitContainer}>
<Button
key="submit"
type="primary"
onClick={handleSubmit}
className={styles.submitButton}
>
Отправить
</Button>
<Button
className={styles.button}
key="back"
onClick={() => setOpenModal(false)}
>
Отмена
</Button>
</div>
}
width={538}
centered
forceRender
>
<Form
form={form}
layout="vertical"
name="form_contact_operators"
initialValues={{ role: "operators" }}
>
<Form.Item name="role" hidden>
<Input type="hidden" />
</Form.Item>
<Form.Item
label="Юридический статус"
name="legal_status"
rules={[{ required: true, message: "Пожалуйста, выберите статус" }]}
className={styles.formItemSelect}
>
<Select
placeholder="Выберите статус"
onChange={handleLegalStatusChange}
options={[
{ value: "individual", label: "Физическое лицо" },
{ value: "legal_entity", label: "Юридическое лицо" },
]}
/>
</Form.Item>
{legalStatus === "legal_entity" && (
<Form.Item
label="Тип юридического статуса"
name="subtype_legal_status"
rules={[
{ required: true, message: "Пожалуйста, выберите тип статуса" },
]}
className={styles.formItemSelect}
>
<Select
placeholder="Выберите тип статуса"
options={[
{ value: "commercial", label: "Коммерческое" },
{ value: "not_commercial", label: "Не коммерческое" },
]}
/>
</Form.Item>
)}
<Form.Item
label="Форма владения"
name="ownership"
rules={[
{ required: true, message: "Пожалуйста, выберите форму владения" },
]}
className={styles.formItemSelect}
>
<Select
placeholder="Выберите форму владения"
options={[
{ value: "ownership", label: "Собственность" },
{ value: "lease", label: "Временное пользование (аренда)" },
]}
/>
</Form.Item>
<Form.Item
label="Тип владения"
name="subtype_ownership"
rules={[
{ required: true, message: "Пожалуйста, выберите тип владения" },
]}
className={styles.formItemSelect}
>
<Select
placeholder="Выберите тип владения"
options={
legalStatus === "individual"
? [{ value: "private", label: "Частная" }]
: [
{ value: "private", label: "Частная" },
{ value: "state", label: "Государственная" },
{ value: "municipal", label: "Муниципальная" },
]
}
/>
</Form.Item>
<Form.Item
label="Тип площадки"
name="type"
rules={[
{ required: true, message: "Пожалуйста, выберите тип площадки" },
]}
className={styles.formItemSelect}
>
<Select
placeholder="Выберите тип площадки"
options={[
{ value: "building", label: "Здание" },
{ value: "land", label: "Земля" },
]}
/>
</Form.Item>
<Form.Item
label="Задайте адрес или укажите точку на карте"
name="address"
rules={[
{
required: true,
message: "Пожалуйста, введите адрес или укажите точку на карте",
},
]}
className={styles.formItem}
>
<Input
placeholder="Введите адрес или укажите точку на карте"
onChange={handleAddressChange}
value={address}
/>
</Form.Item>
</Form>
<YandexMapModal
key={mapKey}
onAddressChange={handleAddressFromMap}
onCoordinatesChange={handleCoordinatesFromMap}
/>
</Modal>
);
}
"use client";
import { useEffect, useRef } from "react";
import styles from "./style.module.css";
export default function YandexMapModal({
onCoordinatesChange,
onAddressChange,
}) {
const mapRef = useRef(null);
const mapInstance = useRef(null);
const placemarkRef = useRef(null);
const initMap = () => {
if (!mapInstance.current && mapRef.current) {
mapInstance.current = new window.ymaps.Map(mapRef.current, {
center: [55.751574, 37.573856],
zoom: 10,
controls: ["zoomControl", "typeSelector"],
});
// Обработчик клика по карте
mapInstance.current.events.add("click", function (e) {
const coords = e.get("coords");
updatePlacemark(coords);
// Получаем адрес по координатам
window.ymaps.geocode(coords).then((res) => {
const firstGeoObject = res.geoObjects.get(0);
const address = firstGeoObject.getAddressLine();
// Передаем данные наружу
onAddressChange(address);
onCoordinatesChange(coords);
});
});
}
};
const updatePlacemark = (coords) => {
if (!mapInstance.current) return;
// Удаляем предыдущую метку, если она есть
if (placemarkRef.current) {
mapInstance.current.geoObjects.remove(placemarkRef.current);
}
// Создаем новую метку
placemarkRef.current = new window.ymaps.Placemark(coords, null, {
preset: "islands#icon",
iconColor: "#0095b6",
draggable: true,
});
// Добавляем метку на карту
mapInstance.current.geoObjects.add(placemarkRef.current);
mapInstance.current.setCenter(coords);
};
useEffect(() => {
const waitForYmaps = () => {
if (window.ymaps && window.ymaps.ready) {
window.ymaps.ready(initMap);
} else {
setTimeout(waitForYmaps, 100);
}
};
waitForYmaps();
return () => {
if (mapInstance.current) {
mapInstance.current.destroy();
}
};
}, []);
return <div ref={mapRef} className={styles.yandexMap} />;
}
Источник: Stack Overflow на русском