Search code examples
htmlreactjstypescriptajaxforms

Trying to stay on the page after submitting, "Failed to fetch" (Used Typescript to write the form)


After trying not to redirect the user that has submitted the form, I had trouble sending the form to my form service.

I setup a Formspark form to use on framer.com. Framer.com forces me to use Typescript which I don't know (I only know HTML CSS and JS right now). I gathered ideas and random information from Formspark's documentation and sitepoint's community. I would really (really) appreciate it if someone created some free time to help solve my problem.

Here's my form (simplified):

import React, { useEffect, useRef, useState } from "react"
import styled from "styled-components"

const SalesForm = styled.form`
  /*styles*/
`
const FormInput = styled.input`
  /*styles*/
`
const Button = styled.button`
  /*styles*/
`
const SuccessMessage = styled.div`
  /*styles*/
`
const ErrorMessage = styled.div`
  /*styles*/
`

export default function Form(props) {
    const captchaRef = useRef<HTMLDivElement>(null)
    const [token, setToken] = useState<string | null>(null)
    const [message, setMessage] = useState<string | null>(null)
    const [isSuccess, setIsSuccess] = useState<boolean | null>(null)

    useEffect(() => {
        console.log("Initializing hCaptcha...")
        if (window.hcaptcha) {
            window.hcaptcha.render(captchaRef.current!, {
                sitekey: "mycaptchacode",
                callback: (token: string) => {
                    console.log("hCaptcha token generated:", token)
                    setToken(token)
                },
            })
        }
    }, [])

    const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
        event.preventDefault()
        if (!token) {
            alert("Please verify that you are human.")
            return
        }

        const formData = new FormData(event.currentTarget)
        console.log("Form Data: ", Array.from(formData.entries()))

        try {
            const response = await fetch("https://submit-form.com/formid", {
                method: "POST",
                body: formData,
            })

            if (response.ok) {
                setMessage("Form successfully submitted!")
                setIsSuccess(true)
                event.currentTarget.reset()
            } else {
                const errorText = await response.text()
                console.error("Error response: ", errorText)
                setMessage(
                    "There was a problem with your submission: " + errorText
                )
                setIsSuccess(false)
            }
        } catch (error) {
            console.error("Submission error: ", error)
            setMessage(
                "There was an error submitting the form: " + error.message
            )
            setIsSuccess(false)
        }
    }

    return (
        <SalesForm id="sales-form" onSubmit={handleSubmit}>
            <script src="https://js.hcaptcha.com/1/api.js" async defer></script>
            <FormInput type="text" id="comp-name" name="comp-name" placeholder="comp-name" required/>
            <div ref={captchaRef}></div>
            <Button type="submit">Send</Button>
            {message &&
                (isSuccess ? (
                    <SuccessMessage>{message}</SuccessMessage>
                ) : (
                    <ErrorMessage>{message}</ErrorMessage>
                ))}
        </SalesForm>
    )
}

I'd like to give a bit more information:

  • handleSubmit was working when it didnt have anything else than the 1st "if" statement that forces the user to verify captcha (therefore, my form had method and action attributes), it didnt also have "async". I added those other lines to stay on the same page after submitting. I was just using a <input type="hidden" name="_redirect" value="redirecturl.com" />. Not staying on the same page was the main thing that dragged me all the way here. I don't want the user to be redirected.
  • Based on all of the above, I suspect that using captcha isn't the problem. And my form service was working until I tried not to redirect.
  • "Failed to fetch" is the error that displays below the form after I tried to send it.

I would really appreciate your help.


Solution

  • From what I can tell, it should work, the frontend logic seems okay. I think you need to check the url where you post. A few things to note

    All you should need to prevent redirection is to call preventDefault on the submit event. So you have event.preventDefault() and that works fine.

    As for the rest, I was not able to replicate your error, "Failed to fetch". I made a replit with your code on https://replit.com/@maxmezzomo/Form-Submit. I just removed the early return and alert for the captcha, I don't think that's the issue either.

    You are however posting to https://submit-form.com/formid which returns a 404 with message "This form does not exist anymore". Even from the browser that's what you get if you click the link. So that's expected behaviour I would say. But it is inline with failed to fetch. I would just think you need to actually have an id if you want to post, so something like https://submit-form.com/<formid> where formid is where you want to post.

    Once you have a url that points to a url expected by server, you should be able to make your request. On Formspark it seems the endpoint expects the following headers https://documentation.formspark.io/examples/ajax.html#axios-with-recaptcha-v2.

    headers: {
      "Content-Type": "application/json",
      Accept: "application/json",
    },
    

    once you add those I think it should work, you probably want the payload to also be json so you can do something like

    JSON.stringify(Object.fromEntries(Object.entries(formData)))
    

    which should give json of an object with the form data, what you will need depends on what the api expects, but you can look into that.

    I updated the replit with this, seems to work, get 200.

    So now the response has code 200, so the form data was successfully submitted. But there's a part of the code that still throws an error which gets caught later, you can't use event currentTarget after handling the event, I don't know how it works exactly, but probably since its async by the time you handle the response of the request currentTarget is null. So the easy way around is to create a reference to the target before you make the request

      const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
        const target = event.currentTarget;
    
        event.preventDefault()
        if (!token) {...
    

    then you can use that variable to call reset.

    Now UI also should show Form successfully submitted!