How to Create Custom Marketo Forms With React
Hello. Itās been a little while, and I have a new job! Iām now at Cockroach Labs and in the coming months Iāll be responsible for creating snazzy looking demo sites and documentation thatāll help you get the most out of CockroachDB. Iāll most likely be using one of the modern JavaScript frameworks to create these demos, E.g: Remix, Next.js, Gatsby or Astro and hope to be focussed on CockroachDBās Serverless offerings.
But, before I got going, I needed to do a little bit of āDeveloper Adminā.
Newsletter Signups with Marketo and React
In each of the demo sites Iāll be creating Iāll want to include a Newsletter signup. This is good for a few reasons, but mainly, I donāt want you to miss out on the good stuff!
Iāve prepared a small demo site to help explain my requirements and have open-sourced the code if youād like to take a look.
- š Demo: https://cockroachdb-custom-marketo-forms-with-react.vercel.app/
- āļø Repo: https://github.com/PaulieScanlon/cockroachdb-custom-marketo-forms-with-react
There is however a problem. At Cockroach Labs we use Marketo for data capture and lead management, and Marketo forms arenāt the easiest to work with. Marketo typically requires you load scripts that inject styled forms directly into the DOM, and as you may already know, directly mutating the DOM when using React is an anti-pattern, not to mention then needing to āhackā styles over the top of the injected styles to ensure your forms look and feel like part of the surrounding site.
In this post Iād like to explain one way Iāve implemented Marketo and youāll see from the demo site that the forms, as promised, are styled the way I want them.
Does Marketo Have an API?
You might be wondering if Marketo has an API, and if so, why not use that to create custom styled forms that can post the form data to the right place?
The reason is actually quite unfathomable.
Marketo does have an API but to use it, a <script />
element
needs to be added to the page which loads some JavaScript, which then injects a styled HTML <form />
element into the
page and adds the MktoForms2
to the window
object. This can then act as an API š„“.
Once the script has loaded and MktoForms2
is available on the window
you can use something similar to the example
below to submit your data:
const handleSubmit = (event, email) => {
event.preventDefault()
window.MktoForms2.getForm(process.env.NEXT_PUBLIC_NEWSLETTER_FORM_ID)
.vals({ Email: email })
.onSuccess(() => {
...
})
.submit()
}
However, loading this script is where things can get a little tricky. My solution is to create a React hook that handles adding the Marketo script to the page, and removes all default styling from the Marketo form.
My solution involves three key ingredients, they are:
- A hidden Marketo form with all styles removed.
- A React hook that loads the script into the page.
- A real form that end users will see and use.
Marketo Form Explained
This component returns an HTML <form />
with an id
of "mktoForm"
+ the formId
. The HTML <form />
element needs
an id
for when the Marketo script has been loaded, the script uses this to inject the default styled form elements;
input(s), label(s), button etc.
Finished Marketo Form
import React, { memo } from 'react';
import PropTypes from 'prop-types';
import useMarketo from '../hooks/use-marketo';
const MarketoForm = memo(({ debug, formId }) => {
useMarketo({
formId: formId,
callback: () => {},
});
return <form id={`mktoForm_${formId}`} className={debug ? 'block' : 'hidden'} aria-hidden='true' />;
});
MarketoForm.defaultProps = {
debug: false,
};
MarketoForm.propTypes = {
/** Show the real Marketo form */
debug: PropTypes.bool,
/** The Marketo Form Id */
formId: PropTypes.string.isRequired,
};
export default MarketoForm;
The <form />
element is set to display: none
/ (hidden) but I have added a debug
prop in case you want to see what
it actually looks like. This component uses a React hook which Iāve named useMarketo
to load the script and Iāll
explain how that works next.
React Hook Explained
I took inspiration from Charlie Dieterās wonderful example:
react-marketo-hook, but made a few changes to help with front-end
performance. You can find the src
for my version of the hook here:
hooks/use-marketo.js.
One of the changes I made uses the whenRendered
callback which Iāve used to search the DOM and remove any attributes
or HTML elements that are not required.
Another change was made because I encountered a Lighthouse
warning pertaining to duplicate element ids. This happened because Marketo adds ids to the form elements. For example
the Email field is automatically assigned an id
of "Email"
. Normally this would be fine, but because I have two
forms on the page and both contain a field named Email, both end up with an id of "Email"
. And as Iām sure you know,
ids need to be unique!
After the form has rendered I search the DOM for all form elements and change their ids so they include the formId
. I
used a similar approach for the for
attribute on the label elements.
For example the Email for
and id
attributes have been changed from "Email"
to "Email_1478"
.
<label for="Email_1478" id="LblEmail_1478" class="mktoLabel mktoHasWidth">
<div class="mktoAsterix">*</div>
Email:
</label>
<input
id="Email_1478"
name="Email"
maxlength="255"
aria-labelledby="LblEmail InstructEmail"
type="email"
class="mktoField mktoEmailField mktoHasWidth"
/>
Finished useMarketo hook
ā¦ and hereās the finished hook!
import { useState, useEffect } from 'react';
const useMarketo = ({ formId, callback }) => {
const [scriptAdded, setScriptAdded] = useState(false);
const [formLoaded, setFormLoaded] = useState(false);
useEffect(() => {
if (scriptAdded) {
if (!formLoaded) {
MktoForms2.loadForm(
`//${process.env.NEXT_PUBLIC_BASE_URL}`,
process.env.NEXT_PUBLIC_MUNCHKIN_ID,
formId,
callback
);
MktoForms2.whenRendered((form) => {
const formElement = form.getFormElem()[0];
const formElementId = form.getFormElem()[0].id.split('_')[1];
/** Remove the style attribute and make for, and id attributes unique */
Array.from(formElement.querySelectorAll('[style]'))
.concat(formElement)
.forEach((element) => {
element.removeAttribute('style');
if (element.hasAttribute('id') && element.tagName !== 'FORM') {
element.setAttribute('id', `${element.getAttribute('id')}_${formElementId}`);
}
if (element.tagName === 'LABEL') {
element.setAttribute('for', `${element.getAttribute('for')}_${formElementId}`);
}
});
/** Remove <span /> from DOM */
Array.from(formElement.querySelectorAll('.mktoInstruction')).forEach((element) => {
element.remove();
});
/** Remove <style /> from DOM */
Array.from(formElement.children).forEach((element) => {
if (element.type && element.type === 'text/css') {
element.remove();
}
});
});
setFormLoaded(true);
}
} else {
if (window.MktoForms2) {
setScriptAdded(true);
} else {
const script = document.createElement('script');
script.defer = true;
script.onload = () => (window?.MktoForms2 ? setScriptAdded(true) : null);
script.src = `//${process.env.NEXT_PUBLIC_BASE_URL}/js/forms2/js/forms2.min.js`;
document.head.appendChild(script);
}
}
}, [scriptAdded]);
};
export default useMarketo;
Real Form Explained
In the repo youāll see components/newsletter-form.js, and hereās how to use it.
<NewsletterForm formId={process.env.NEXT_PUBLIC_NEWSLETTER_FORM_ID} />
This component, among other things contains the following:
- An instance of the hidden components/marketo-form.js.
- Standard HTML
<form />
,<label />
and<input />
elements which you can style however you like! - A
handleSubmit
function that submits data usingwindow.MktoForms2.getForm
. - An instance of another hook called hooks/use-reducer.js which handles loading states.
Finished Real Form
ā¦ and hereās the finished form!
import React, { useState, useReducer, Fragment } from 'react';
import PropTypes from 'prop-types';
import { initialState, reducer } from '../hooks/use-reducer';
import MarketoForm from './marketo-form';
const NewsletterForm = ({ formId }) => {
const [email, setEmail] = useState('');
const [state, dispatch] = useReducer(reducer, initialState);
const handleSubmit = (event) => {
event.preventDefault();
dispatch({
type: 'isSubmitting',
});
window.MktoForms2.getForm(formId)
.vals({ Email: email })
.onSuccess(() => {
dispatch({
type: 'success',
});
setEmail('');
return false;
})
.submit();
};
return (
<Fragment>
<h3 className='m-0'>Newsletter Form</h3>
<p>Example form used to capture email addresses only.</p>
<div className='bg-white rounded border border-brand-gray-b p-8 sm:px-16 pt-16'>
<h3 className='m-0 font-bold text-brand-deep-purple'>Signup to Our Newsletter</h3>
<small className='block mb-8 text-brand-gray'>* Required fields</small>
<form onSubmit={handleSubmit} className='form'>
<label className='form--label'>
<span className='form--label-text'>
Email<span className='form--label-required'>*</span>
</span>
<input
className='form--input'
type='email'
required
placeholder='you@example.xyz'
value={email}
onChange={(e) => {
setEmail(e.target.value);
}}
/>
</label>
<span className='form--announce-container'>
{state.isSubmitting ? <span className='text-brand-orange'>Submitting...</span> : null}
{state.success ? <span className='form--announce-success'>Thanks for signing up.</span> : null}
</span>
<button type='submit' className='transition-all form--button-submit' disabled={state.isSubmitting}>
{state.isSubmitting ? 'Please wait...' : 'Subscribe'}
</button>
</form>
<small className='preferences--cta'>
To update your email preferences visit{' '}
<a
href='https://www.cockroachlabs.com/email-preferences/'
target='_blank'
rel='noreferrer'
className='text-brand-deep-purple hover:!text-brand-primary'
>
cockroachlabs.com
</a>
</small>
<MarketoForm debug={false} formId={formId} />
</div>
</Fragment>
);
};
NewsletterForm.propTypes = {
/** The Marketo Form Id */
formId: PropTypes.string.isRequired,
};
export default NewsletterForm;
And thatās it, totally customizable and performant Marketo forms in React. These forms are āthe real dealā so if youād like to hear about all the latest news from Cockroach Labs, go ahead and sign up!
If you have any issues, comments or improvements please feel free to open an issue or PR on the repo and Iāll be happy to take a look or, if youād prefer, please come find me on Twitter: @PaulieScanlon.
See you around the internet!
Paul.