Skip to content

kimfucious/netlify-forms-formik

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

93 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Netlify Forms with Formik

Netlify Status

This repo was created to test and document how to get Netlify Forms working with Formik in a Create React App build and add ReCaptcha if desired.

Netlify Forms is a super cool (and free) feature for sites hosted on the Netlify platform.

Formik is a great library that "removes the tears" 😭 from form creation in React.

The problem is that forms rendered via React don't work out of the box with Netlify forms.

tl;dr

You need to add a hidden HTML form that mimics the fields in your Formik form in order to get Netlify forms to work.

☝️ Note that static site generators like Gatsby and Hugo are different creatures and require a different solution (they pretty much just work) than would a Create React App build. Documentation here is solely pertinent to CRA.

Reading material

This is a very informative article; however--for me, at least--it took a while to realize that there needs to be a (hidden) mirror HTML form of the Formik form being rendered by React. And the info surrounding reCaptcha is a bit lean.

Reading this may also be helpful.

Initial Setup (doesn't work out of the box)

Without extra setup, submitting a React form when hosted on Netlify will return a 404 error.

This error can be a bit misleading because, when you look at the code, the form is there. It should be found. So what's happening?!

The reason is that Netlify's form-bots can't see JavaScript rendered form code. It's not that they're doing anything wrong, it's just kinda how things are. Thankfully, they're nice enough to have given us a way to work around this issue.

So in order to get this working, there's a few more steps to be done.

Steps to Get Things Working

1) Add a static HTML version of the form

Add the following form block just below the initial <body> element tag in /public/index.html:

<form data-netlify="true" hidden name="contact" netlify-honeypot="bot-field">
  <input type="text" name="username" />
  <input type="email" name="email" />
  <input name="bot-field" type="hidden" />
</form>

According to the maesters, you can--rather than add this code block to /public/index.html--create a separate HTML file that includes your form code block somewhere in the build, and it will get picked up. I, haven't tried this, personally, but the maesters said it is so.

☝️ This is obviously just an example. Your form will most likely have differing fields. So make sure that there is one-to-one match here, whereby each input corresponds with a respective input element/Field component in your Formik form.

2) Add additional initial values to the Formik form

a) Add a bot-field and form-name field to initialValues of the Formik form:

      <Formik
        initialValues={{
          "bot-field": "",
          "form-name": "contact",
          email: "",
          username: ""
        }}

While the honeypot is a novel concept, it's not super-effective against spam bots, so you might want to check out the section on adding reCaptcha, which is a more robust solution.

πŸ“° In February 2019, Netlify announced that all form submissions will be filtered for spam, using Akismet. Huzzah huzzah! πŸŽ‰

b) Add those (hidden) fields to the Formik form itself:

<Field type="hidden" name="bot-field" />
<Field type="hidden" name="form-name" />

πŸ’Ύ The relevant code to see how this works can be found in /public/index.html and FormikForm.js within this repo.

Adding ReCaptcha

tl;dr

Use a library to add reCaptcha (e.g. reaptcha) and be sure to send the reCaptcha response along with your form submission.

😱 reCaptcha is notoriously easy to mistype, and reaptcha adds another nuance to the pot. I've used abbreviations in variable declarations to help avoid issues around that.

Setup

There are a lot of libraries out there for adding reCaptcha to a React site. And most reCaptcha libraries are not especially clear in their documentation, IMHO, with to how to get the end-to-end solution working, esp. getting at the reCaptcha response token.

After a bit of trial and error, I settled on reaptcha. It's clean and documented well.

There's actually only one key step to get reCaptcha working with Netlify Forms, which is to send the reCaptcha response token with your form data. The main work here is to get a hold of the reCaptcha response, so we can do just that.

The steps are:

  1. Load reCaptcha
  2. Execute reCaptcha onSubmit (for reCaptcha v2 invisible)
  3. Retrieve the reCaptcha response
  4. Submit the reCaptcha response along with the form data.

To accomplish this, this example leverages the built-in callback functions of reaptcha along with some React Hooks.

The Reaptcha block looks like this:

<Reaptcha
  ref={rcRef}
  sitekey="your site key goes here"
  data-netlify-recaptcha="true"
  onError={onError}
  onExpire={onExpire}
  onVerify={onVerify}
  onLoad={() => onLoad(() => resetForm)}
  size="invisible"
/>

onLoad

onLoad is set as an attribute on the Reaptcha element in the FormikForm.js file.

In this example, the onLoad callback function is used to load the clearForm action into a React State Hook value, resetForm:

const onLoad = resetForm => {
  console.log("loaded...");
  setLoaded(true);
  setFormReset(resetForm);
};

This is kinda hacky, but in order to clear the form after a successful form submission (or for some other reason), I wanted access to that action, which is passed down as a prop in the Formik block scope but is not accessible outside it.

There may be (probably is) a better way to do this, but I got bored thinking about it and moved on.

At this point, nothing happens until the user fills out and submits the form.

useRef

Once reCaptcha is loaded, reCaptcha needs to get executed "manually". Manually calling execute() is necessary for the support of reCaptcha v2 invisible, which is set via the size attribute on the Reaptcha element, because clicking on the reCaptcha widget is not possible when it's not visible.

Though that may seem plainly obvious to some, I'll re-emphasize it anyhow: It's nature of the invisible reCaptcha beast to be invisible, and thus we must wire the user action of submitting the form to reCaptcha execution.

πŸ€– Only reCaptcha v2 invisible is documented here, but with a few tweaks you should be able to get other reCaptcha types (e.g. "I am not a robot") working.

In order to execute reCaptcha, a React Ref Hook has been setup for the Reaptcha element.

const rcRef = useRef(null);

The above assignment, plus the associated ref attribute on the Reaptcha element, allows execute() to be called on rcRef.current, which is done when the form is submitted in the Formik onSubmit function.

onSubmit={values => {
  setIsSubmitting(true);
  setFormValues({ ...values });
  setExecuting(true);
  rcRef.current.execute();
}}

If you're familiar with Formik, this is where all the action usually happens; however, I've moved some of the action out of this function and into a separate function, handleSubmit, which gets triggered by a React Effect Hook.

The reason for separating this out is that there is a delay between executing reCaptcha, receiving the reCaptcha response, putting it somewhere (e.g. state) where it can be accessed, and having that value ready to inject into the form data on submission. And I got tired of shooting blanks when submitting my form. There's probably a better way to do this as well.

onVerify

onVerify is set as an attribute on the Reaptcha element in the FormikForm.js file.

The onVerify callback runs after rcRef.current.execute() and returns the reCaptcha response (in the form of a token).

This token is stored in the React Hook State value token.

handleSubmit

In this example, a React Effect Hook, has been setup to look for token changes in state.

useEffect(() => {
  const handleSubmit = async (formValues, token) => {
    const data = {
      ...formValues,
      "g-recaptcha-response": token
    };
    const options = {
      method: "POST",
      headers: { "Content-Type": "application/x-www-form-urlencoded" },
      data: qs.stringify(data),
      url: "/"
    };
    try {
      await axios(options);
      setMsgSent(true);
      formReset();
    } catch (e) {
      setErrMsg(e.message);
    }
  };
  if (token) {
    handleSubmit(formValues, token);
  }
}, [formReset, formValues, token]);

πŸ§™ If there were magic, this is where it might happen.

After the onVerify callback returns the token and places it in state, the effect hook will trigger the handleSubmit function.

handleSubmit builds the axios configuration (note the content type!) and submits the form.

The reCaptcha response, token, gets injected into data just prior to form submission, and its value is assigned to the key, g-recaptcha-response.

If axios is successful, the form gets reset with formReset(), which is actually Formik's resetForm that was populated into state at onLoad.

🌈 And Bob's your uncle! πŸ¦„