React Hooks - лучшее место для вызова функции, которая зависит от состояния из «одноразового» useEffect? - PullRequest
2 голосов
/ 23 февраля 2020

Я пытаюсь реализовать карты Google с помощью React Hooks без использования каких-либо сторонних библиотек. Я написал код, который позволяет генерировать скрипт, который выбирает карты Google, используя URL, как показано ниже. Но я не знаю, как правильно вызывать эту функцию генерации скрипта (createScriptLoadMap() в нашем случае), так как вызов ее в useEffect, который вызывается только один раз, кажется, дает мне ошибку:

You have included the Google Maps JavaScript API multiple times on
this page. This may cause unexpected errors.

Я хочу знать, почему я получаю эту ошибку и как ее можно устранить. Вот ссылка на стек, где я старался изо всех сил сделать минимальный код для моей ситуации:

https://stackblitz.com/edit/react-hsl4dn?file=Hello.js

Редактировать: я также делаю get запрос от useEffect, так что это должен быть «один раз» useEffect, но я должен также вызвать createScriptLoadMap() один раз, и эта функция зависит от состояния, установленного useEffect, который не не делайте немедленно!

Есть ли способ, при котором происходит запрос get, задает mapCenter и затем выполняется createScriptLoadMap()?

Ответы [ 2 ]

2 голосов
/ 23 февраля 2020

Ваш useEffect хук выполняется только один раз. Вы получите это сообщение, скорее всего, из-за горячей перезагрузки и повторного подключения компонента Map.

Чтобы избежать этого, просто используйте свойство окна, которое вы устанавливаете window.initMap, чтобы проверить, являются ли сценарии карты загружено:

const createScriptLoadMap = () => {
  if (!window.initMap) {
    var index = window.document.getElementsByTagName("script")[0];
    var script = window.document.createElement("script");
    script.src =
      "https://maps.googleapis.com/maps/api/js?key=AIzaSyDurZQBXjtSzKeieXwtFeGe-jhZu-HEGQU&libraries=drawing&callback=initMap";
    script.async = true;
    script.defer = true;
    index.parentNode.insertBefore(script, index);
    window.initMap = true;
  }
};

РЕДАКТИРОВАТЬ

Что касается центрирования карты после извлечения некоторых данных, вам также следует go с крючками и useEffect в уже есть mapCenter объект состояния. Сначала используйте script.load, чтобы увидеть, когда скрипт загружен. Вызовите initMap независимо от того, загружен скрипт или нет, чтобы получить объект google.maps.Map и сохраните его, используя состояние для последующего использования, иначе, если скрипт уже был загружен, просто назначьте объект карты:

const createScriptLoadMap = () => {
  if (!window.initMap) {
    var index = window.document.getElementsByTagName("script")[0];
    var script = window.document.createElement("script");
    script.src =
      "https://maps.googleapis.com/maps/api/js?key=AIzaSyDurZQBXjtSzKeieXwtFeGe-jhZu-HEGQU&libraries=drawing&callback=initMap";
    script.async = true;
    script.defer = true;
    script.onload = () => {
      initMap();
    };
    index.parentNode.insertBefore(script, index);
    window.initMap = true;
  } else {
    initMap();
  }
};

Затем измените initMap функция для инициализации или присвоения объекта google.maps.Map для элемента DOM #map (, который не будет создавать новую карту, если мы уже присвоили элемент ранее ):

const initMap = () => {
  let googleMap = new google.maps.Map(document.getElementById("map"), {
    center: { lat: 37.978534, lng: 23.743830 }, //CURRENTLY STATIC
    //center: {mapCenter} //ACTUAL
    zoom: 14
  });

  // Save the map object created for later usage
  setMap(googleMap);

  // axios.get(`${BASE_URL_LOCAL}/outlet/list?page=1&limit=50`).then(res => {
  //   let center = averageGeolocation(res.data.data.outlets);
  //   console.log(props.mapCenter);
  //   setFetchedData(res);
  //   setMapCenter(center) //THIS CENTER IS REQUIRED IN LINE 22
  // });

  // Suppose get an Axios response after 2 seconds
  setTimeout(() => {
    // Center map at NY Brooklyn using state (effect for mapCenter will be triggered)
    setMapCenter({ lat: 40.674282, lng: -73.943060 });
  }, 2000);
};

Это создаст объект google.aps.Map и сохранит его в состоянии setMap для последующего использования. Теперь, когда вы хотите обновить центр карты ( или любые другие свойства / методы карты ), просто установите useEffect hook на mapCenter объект состояния и затем используйте объект map для изменения Карта:

const [mapCenter, setMapCenter] = useState({});
const [fetchedData, setFetchedData] = useState({});
const [map, setMap] = useState();

useEffect(() => {
  createScriptLoadMap();
}, []);

useEffect(() => {
  if(map) {
    map.setCenter(mapCenter)
  }
}, [mapCenter]);

Проверьте мои раздвоенные и обновленные stackblitz. Вы увидите, что карта начинается из Афин и через 2 секунды ( предположим, что у нас есть асинхронный топор ios call ), она центрирует карту в Нью-Йорк Бруклин. ( Я создал ключ API Карт Google для демонстрации, я удалю после вашего ответа ).

0 голосов
/ 23 февраля 2020

https://stackblitz.com/edit/react-ohtdra?file=Hello.js

Крючки в этом случае не помогут. Даже если вы вернете функцию очистки из useEffect и удалите element из head. Поскольку этот сценарий создает массу других элементов в head (например, стили) , и простое удаление одного элемента не поможет.

Я должен сказать, что делать вещи без вводить в нее React, но если вам нужно, вы можете. Например, добавьте что-нибудь в тег сценария, проверьте это в следующий раз и пропустите createScriptLoadMap. Как это:

const createScriptLoadMap = () => {
  var gScript = document.querySelector("script[data-setted]");
  var isSetted = gScript && gScript.getAttribute("data-setted");

  if (!isSetted) {
    var index = document.getElementsByTagName("script")[0];
    var script = document.createElement("script");
    script.src =
      "https://maps.googleapis.com/maps/api/js?key=AIzaSyDurZQBXjtSzKeieXwtFeGe-jhZu-HEGQU&libraries=drawing&callback=initMap";
    script.async = true;
    script.defer = true;
    script.setAttribute("data-setted", true);
    index.parentNode.insertBefore(script, index);
    window.initMap = initMap;
  } else {
    // I'm not sure about this, can cause a problem
    // but without it stackblitz doesn't render anything
    initMap();
  }
};
...