Как вы можете остановить загрузку стороннего Javascript в функции очистки?

#reactjs #use-effect #marketo

#reactjs #использование-эффект #marketo

Вопрос:

Я пытался загрузить форму Marketo на свой сайт Gatsby, и я нашел несколько полезных советов о том, как это сделать в Stack Overflow, но во всех примерах есть проблема, которую я, похоже, не могу решить.

Вот компонент:

 import React from 'react'
import useMarketo from '../../hooks/useMarketo'

export default function MarketoForm({ formId }) {
    const baseUrl = '//XXX-XXX.marketo.com'
    const munchkinId = 'XXX-XXX-XXX'

    useMarketo(baseUrl, munchkinId, formId)

    return <form id={`mktoForm_${formId}`} />
}
 

А вот и хук:

 import { useState, useEffect } from 'react'

function appendScript(baseUrl, setScriptLoaded) {
    if (window.MktoForms2) return setScriptLoaded(true)

    const script = document.createElement('script')
    script.src = `${baseUrl}/js/forms2/js/forms2.min.js`
    script.onload = () => (window.MktoForms2 ? setScriptLoaded(true) : null)
    document.body.appendChild(script)
}

export default function useMarketo(baseUrl, munchkinId, formId) {
    const [formIsLoaded, setFormIsLoaded] = useState(false)
    const [scriptLoaded, setScriptLoaded] = useState(false)

    useEffect(() => {
        if (scriptLoaded amp;amp; !formIsLoaded) {
            const windowGlobal = typeof window !== 'undefined' amp;amp; window
            windowGlobal.MktoForms2.loadForm(baseUrl, munchkinId, formId).whenRendered(setFormIsLoaded(true))
            return
        }
        appendScript(baseUrl, setScriptLoaded)
    }, [formIsLoaded, scriptLoaded, baseUrl, munchkinId, formId])
}
 

Проблема в том, что если я уйду со страницы и быстро вернусь, форма загрузится несколько раз.

Форма Marketo появляется много раз

Я не уверен, как очистить его в функции useEffect. Проблема в том, что loadForm функция еще не вернулась, и когда я ухожу со страницы, а затем возвращаюсь, она фактически получает это возвращение, а затем отправляет другой запрос.

Кажется, я не могу найти решение для этого, используя состояние или функцию очистки. AbortController() казался действительно многообещающим, но я тоже не смог найти решение. Похоже, это должно быть легко исправить. Я не первый человек с этой проблемой, поскольку она появлялась здесь раньше, а также на форумах Marketo, но мне еще предстоит найти адекватное решение.

Заранее спасибо за любые советы!

Комментарии:

1. Не могли бы вы использовать оператор возврата useEffect для его очистки?

2. идеально ли загружать скрипт только один раз за сеанс?

Ответ №1:

MkToForms2 Поддерживает обратный вызов 4-го параметра, который вызывается с формой после loadForm завершения. Мой совет — использовать функцию очистки эффекта, чтобы отслеживать, когда useMarketo перехват отключен, и если он отключен, используя loadForm обратный вызов для удаления формы.

         windowGlobal.MktoForms2.loadForm(
          baseUrl,
          munchkinId,
          formId,
          (form) => {
            const $form = form.getFormElem();
            // if this was cancelled do not show it and remove it
            if (cancelled) {
              $form.remove();
              return;
            }
            // if not cancelled this is still valid, lets show it and update form loading status
            setFormIsLoaded(true);
          }
        );
 

Также, сделав appendScript возврат обещанием (не требуется), вы можете очистить часть кода и удалить некоторые дополнительные зависимости от loadForm эффекта, который вам, вероятно, не нужен. Ниже мой шаг.

 function appendScript(baseUrl) {
  return new Promise((resolve, reject) => {
    const existingScript = document.body.getElementById(baseUrl);

    if (existingScript) {
      return resolve(true);
    }

    if (window.MktoForms2) {
      resolve(true);
    }

    const script = document.createElement("script");
    script.src = `${baseUrl}/js/forms2/js/forms2.min.js`;
    script.onload = () => resolve(window.MktoForms2);
    script.onerror = reject;
    document.body.appendChild(script);
  });
}

export default function useMarketo(baseUrl, munchkinId, formId) {
  const [formIsLoaded, setFormIsLoaded] = useState(false);
  useEffect(() => {
    let cancelled = false;
    const loadForm = async () => {
      const scriptLoaded = await appendScript(baseUrl);
      if (scriptLoaded) {
        const windowGlobal = typeof window !== "undefined" amp;amp; window;
        windowGlobal.MktoForms2.loadForm(
          baseUrl,
          munchkinId,
          formId,
          (form) => {
            const $form = form.getFormElem();
            // if this was cancelled do not show it and remove it
            if (cancelled) {
              $form.remove();
              return;
            }
            // if not cancelled this is still valid, lets show it and update form loading status
            setFormIsLoaded(true);
          }
        );
      }
    };
    loadForm();
    return () => {
      cancelled = true;
    };
  }, [baseUrl, munchkinId, formId]);
}
 

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

   if (!cancelled) {
     $form.show();
  }
 

Это не loadForm помешает выполнению при перемещении вперед и назад, но должно гарантировать, что только текущему loadForm разрешено отображать и сохранять форму на странице.

Ответ №2:

 const scripts = {};
function importScript(url) {
   if (!scripts[url]) {
      const script = document.createElement('script');
      scripts[url] = {
         loaded: false,
         fail: false,
         resolves: [],
         rejects: [],
      }    
      script.onload = ()=> {
         scripts[url].loaded = true;
         scripts[url].resolves.forEach(r=> r());
      }
      script.onerror = (err)=> {
          scripts[url].fail = true;
          scripts[url].rejects.forEach(r=> r());
      }
   }
   if (scripts[url].fail) return Promise.reject();
   if (scripts[url].loaded) return Promise.resolve();
   return new Promise((r, j)=> {
      scripts[url].resolves.push(r);
      scripts[url].rejects.push(j);
   });
}

 
 export default function useMarketo(baseUrl, munchkinId, formId) {

    useEffect(() => {
       importScript(baseUrl).then(()=> {
          windowGlobal.MktoForms2.loadForm(baseUrl, munchkinId, formId);
       }, console.error);
    }, [formIsLoaded, scriptLoaded, baseUrl, munchkinId, formId])
}