By Paul Scanlon

How to Create Custom Marketo Forms With React

  • Next.js
  • React
  • Marketo

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.

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 using window.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!

TTFN

Paul.

Hey!

Leave a reaction and let me know how I'm doing.

  • 0
  • 0
  • 0
  • 0
  • 0
Powered byNeon