I have been trying to load a Marketo form on my Gatsby site, and I have found some helpful tips on how to do that on Stack Overflow, but in all of the examples, there is a problem that I can't seem to solve.
Here is the component:
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}`} />
}
And here is the hook:
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 && !formIsLoaded) {
const windowGlobal = typeof window !== 'undefined' && window
windowGlobal.MktoForms2.loadForm(baseUrl, munchkinId, formId).whenRendered(setFormIsLoaded(true))
return
}
appendScript(baseUrl, setScriptLoaded)
}, [formIsLoaded, scriptLoaded, baseUrl, munchkinId, formId])
}
The problem is if I navigate away from the page, and return quickly, the form will load multiple times.
Marketo Form appears many times
I'm not sure how to clean it up in the useEffect function. The problem is that the loadForm
function hasn't returned yet, and when I navigate away from the page, and then come back, it actually gets that return, and then sends out another request.
I can't seem to find a fix for this using state, or a cleanup function. AbortController() seemed really promising, but I couldn't find a solution there either. It seems like it should be an easy fix, though. I'm not the first person with this problem as it has appeared here before, as well as on the Marketo forums - but I have yet to find an adequate solution.
Thanks in advance for any advice!
The MkToForms2
supports a 4th parameter callback, which is called with the form once the loadForm
is completed. My advice is to use the effect cleanup function to keep track of when the useMarketo
hook is unmounted and if it is unmounted using the loadForm
callback to remove the form.
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);
}
);
Also by making the appendScript
return a promise (not needed) you can cleanup some of the code and remove some extra dependencies on the loadForm
effect you probably don't need. Below is my pitch.
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" && 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]);
}
I'm not sure if the form removals will show a flash of an added form and then removed form, but if it does then you should add a default css class to hide all the added forms and instead of removing the forms if cancelled, showing them if not cancelled:
if (!cancelled) {
$form.show();
}
This won't prevent loadForm
from executing when navigating back and forth, but it should ensure that only the current loadForm
is allowed to show and keep the form on the page.