Яндекс карта в модальном окне в приложении Next

Рейтинг: 0Ответов: 1Опубликовано: 16.07.2025

Пишу на 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} />;
}

Ответы

▲ -1

Проблема оказалась что у блока body стояла высота 100vh и из-за этого Яндекс карта рассчитывалась как то не верно! Убрав этот стиль стало все корректно работать.