Incorrect render on form action

Site: https://infallible-mclean-a57ed9.netlify.app/

I am experiencing inconsistent rendering using Netlify Forms and the action to trigger an update.

The form is here: https://infallible-mclean-a57ed9.netlify.app/contact-neue.

Here is the relevant code:

<form
      name="contact"
      className="form"
      method="POST"
      action="/contact-neue#success"
      data-netlify="true"
      data-netlify-honeypot="bot-field"
>
    ...
</form>

If I submit the form the rendered HTML is this, which is incorrect:

But if I hit https://infallible-mclean-a57ed9.netlify.app/contact-neue#success direct, the HTML is correct:

This is the code for the page:

    <Layout>
      <SEO title="Contact Neue" />
      <div className="container">
        <article className="contact-page">
          {location.hash === '#success' ? (
            <ContactSuccess />
          ) : (
            <ContactForm
              title={contactData.title}
              introCopy={contactData.introCopy}
            />
          )}
        </article>
      </div>
    </Layout>

Any idea what might be going on?
Thanks.

Yeah, your code seems to be rendering the contact success component when there’s the hash. The hash might only be working when it exists on page load only, not sure if it keeps listening for a hash change later or not.

An alternative solution might be to use AJAX based form and update DOM with JavaScript.

Here’s a vanilla JS example (truncated from my website’s code, might be untested), if it helps:

var form = document.querySelector('.form');
form.addEventListener('submit', function(e)
  {
    e.preventDefault();
    var XHR = new XMLHttpRequest();
    var FD = new FormData(form);
    XHR.addEventListener('load', function(event)
      {
        // submit success
      }
    XHR.addEventListener('error', function(event)
      {
        // submit fail
      }
  }

Hey @mikeriley131 :wave:

First, super neat site. Design is awesome :slight_smile:

Did quite a bit of experimentation and code-checking on what’s going on here. Definitely a fringe case with React hydrating / Gatsby’s build-ahead workflow. To summarize what you’re experiencing, it’s a non-determinant behavior that can be viewed by checking the class of the <div> that’s a direct child of the article.contact-page. When the location hash is not #success, the aforementioned ‘target div’ should have the class contact-page__content-wrapper but when the hash is present, the target div should instead maintain the class contact-page__success.

All the while, from what I can tell, the markup using either class appears to finally render the same. Is that true?

Here’s what I found. When you browse to the path /contact-neue for the first time (or on a hard refresh), the contact-page__content-wrapper is used and the form is displayed within. This is (I believe) desired / normal behavior. From that point, if you manually add the #success path to the URL (without filling out the form), the success message appears and the contact-page__success class is applied. This is akin to the Reach router making an intra-site navigation so it makes sense that the router would be fully hydrated and updated with the location.

That said, if you cold-load (or hard refresh) directly on the /contact-neue#success path, the success message is displayed but the contact-page__content-wrapper class is used. For the sake of the form, when you POST a form to Netlify, the response simply contains the static page content for the targeted action attribute on that form… so the workflow is akin to cold-loading the /contact-neue#success path. It’s not an intra-site navigation as far as the React runtime is concerned; you’re fully loading a new page and then re-initializing the React runtime at that point. (Which is why you actually get a flash of the form before it switches to the success page – the statically generated HTML was the form, not the success).

So the real question is, why, on cold-load / hard refresh of the /contact-neue#success path, does React go ahead with rendering the <ContactSuccess /> component, but for whatever reason not actually replace the class on the already-existing div from contact-page__content-wrapper to contact-page__success. I’m wondering if it’s a mis-hydration concern since the underlying HTML structure contained from that div down doesn’t actually change; just that one class tag.

In your page component declaration you’re doing: const ContactNeue = ({ data, location }) => then directly using the location object lower down in your ternary. I wonder if somehow there’s just a disconnect in the value of that particular prop between build time and runtime when you load the #success path directly. Could you try instead going from

import React from 'react';
import PropTypes from 'prop-types';
import { graphql } from 'gatsby';

import { Layout } from '../components/layout';
import SEO from '../components/seo';

import { ContactForm } from '../components/contactForm';
import { ContactSuccess } from '../components/contactSuccess';

const ContactNeue = ({ data, location }) => {
  const contactData = data.neueamsterdam.page.contactPage;

  return (
    <Layout>
      <SEO title="Contact Neue" />
      <div className="container">
        <article className="contact-page">
          {location.hash === '#success' ? (
            <ContactSuccess />
          ) : (
            <ContactForm
              title={contactData.title}
              introCopy={contactData.introCopy}
            />
          )}
        </article>
      </div>
    </Layout>
  );
};

to something more like:

import React, { useState, useEffect } from 'react';
import PropTypes from 'prop-types';
import { graphql } from 'gatsby';

import { Layout } from '../components/layout';
import SEO from '../components/seo';

import { ContactForm } from '../components/contactForm';
import { ContactSuccess } from '../components/contactSuccess';

const ContactNeue = ({ data, location }) => {
  const contactData = data.neueamsterdam.page.contactPage;

  const [showSuccess, setShowSuccess] = useState(false)

  useEffect(() => {
    if (location.hash === '#success') {
      setShowSuccess(true)
    }
  }, location.hash)

  return (
    <Layout>
      <SEO title="Contact Neue" />
      <div className="container">
        <article className="contact-page">
          {showSuccess ? (
            <ContactSuccess />
          ) : (
            <ContactForm
              title={contactData.title}
              introCopy={contactData.introCopy}
            />
          )}
        </article>
      </div>
    </Layout>
  );
};

? I’m recommending this because useEffect() guarantees that the effect chunk of code will run only at runtime (on a browser, not during build), then the state change would guarantee an effective re-render, and the useEffect dependency should track with the location.hash.

If you’re up for giving that a try, let me know if it works!


Jon

Yes! Of course. Thank you Jon. I can’t believe that didn’t occur to me earlier. Really appreciate the in-depth response.

2 Likes