How to Add EmailOctopus Form to a React App

EmailOctopus form is a script tag, this post shows how to make that work with React (using useEffect and useRef).

Somehow EmailOctopus form is a <script> tag that should be put into an HTML node where you want it to be rendered (see here)

e.g.

<script async src="https://xxx.js" data-form="xxx"></script>

For React app, inserting this script component directly into the component you try to render, like the following, won't work

export default function Home() {
  return (
    <PageShell>
      <Center>
        <script
          async
          src='https://eocampaign1.com/form/xxx.js'
          data-form='xxx'
        ></script>
      </Center>
    </PageShell>
  );
}

This is because although React will render this script component it won't be executed.

The reason why the execution doesn’t happen is because internally React uses innerHTML on the respective elements to actually set/inject/flush the changes into the DOM. The HTML5 spec specifies that user agents (browsers) must not execute the script tags set via innerHTML (more info at MDN). [source]

The way to get script to execute it to use useEffect and create a script tag after render (and remove it if needed).

export default function Home() {

  useEffect(() => {
    const script = document.createElement('script');
  
    script.src = "https://eocampaign1.com/form/xxx.js";
    script.async = true;
    script.setAttribute('data-form', 'xxx');

    document.body.appendChild(script);
  
    return () => {
      document.body.removeChild(script);
    }
  }, []);
  return (
    <PageShell>
      <Center>
        {/* <script
          async
          src='https://eocampaign1.com/form/xxx.js'
          data-form='xxx'
        ></script> */}
      </Center>
    </PageShell>
  );
}

This doesn't quite work, because the form is appended to the bottom of the page/component, but we are getting close.

I think what the EmailOctopus script does is basically to create a form and attach it to the script tag's parent HTML node, so we need to append it to the right place. This is where React Ref comes in to help us find the find the right DOM node to attach.

Refs and the DOM – React
A JavaScript library for building user interfaces

Here is the final implementation that works

export default function Home() {
  const myRef = useRef<HTMLDivElement>();
  useEffect(() => {
    const script = document.createElement('script');

    script.src =
      'https://eocampaign1.com/form/xxx.js';
    script.async = true;
    script.setAttribute('data-form', 'xxx');

	const myRefNode = myRef.current
    myRefNode.appendChild(script);

    return () => {
      myRefNode.removeChild(script); //Except this doesn't work, see below how to fix it
    };
  }, []);
  return (
    <>
      <PageShell>
        <Center ref={myRef}></Center>
      </PageShell>
    </>
  );
}

The end results

Actually there still is one problem, the cleanup function will not work because it is not the script node we want to clean up by rather the form node generated by the script, so change the clean up function from this

	return () => {
      myRefNode.removeChild(script);
    };

to this

    return () => {
      const nodes = document.querySelectorAll(`[data-form="${id}"]`);
      nodes.forEach(function (node) {
        node.parentNode.removeChild(node);
      });

Finally, the alternative method is to just use EmailOctopus's API and you have full control over the form in your app (I think the thing you need to handle on your own is reCAPTCHA)

Create Contact Of A List – EmailOctopus API Documentation