skip to Main Content

I’m trying to use the new useFormStatus() hook on a react + next.js project, but it’s not working as I expect:

import { useFormStatus } from "react-dom";

type SurveyResultProps = {
    answers: Record<string, Answer>
}

const SurveyResult: React.FC<SurveyResultProps> = ({ answers }: SurveyResultProps) => {
    const [resultText, setResultText] = useState('');
    const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
        event.preventDefault();
        const text = await getResultText(answers);
        setResultText(text);
    };

    function Submit() {
        const asdf = useFormStatus();
        console.log(asdf);
        const { pending } = asdf;
        return <Button type="submit" disabled={pending} text={pending ? "Submitting..." : "Submit"} />
    }
    return <div>
        <form onSubmit={handleSubmit}>
            <Submit />
        </form>
        {resultText && (
            <pre>{resultText}</pre>
        )}
    </div>;
}

The server action works and the resultText is correctly displayed on the page. However…

  • the hook always says the form isn’t pending
  • the button never disables, and
  • the button text never changes from Submit to Submitting...

Here’s console.log output:

{pending: false, data: null, method: null, action: null}
{pending: false, data: null, method: null, action: null}
{pending: false, data: null, method: null, action: null}
{pending: false, data: null, method: null, action: null}

I see in the docs this hook is marked as a "canary" feature, is there anything else I need to do to opt into it? Or is something wrong with my implementation?

2

Answers


  1. You are defining an onSubmit handler that also calls event.preventDefault(), so the form submit event is cancelled.

    If you look at usage examples for useFormStatus in the React documentation, none of them are cancelling the form’s submit event. Specifically, from the React documentation:

    action: A reference to the function passed to the action prop on the parent . If there is no parent , the property is null. If there is a URI value provided to the action prop, or no action prop specified, status.action will be null.

    So you can update your code to not use event.preventDefault or the onSubmit handler and instead pass a reference to an action that wraps getResultText:

    const doAction = async () => {
      const text = await getResultText(answers)
      setResultText(text)
    }
    
    return (
      <div>
        <form action={doAction}>
          <Submit />
        </form>
        {resultText && (
           <pre>{resultText}</pre>
        )}
      </div>
    )
    

    You can also define the action inline:

    <form action={async (formData) => {
      // Optionally use any form fields from formData
      const text = await getResultText(answers)
      setResultText(text)
    }}>
      <Submit />
    </form>
    
    Login or Signup to reply.
  2. Most simple solution would be to use separate the submit component in another file and make it a client component using ‘use client’ hook.

    //app/../Submit.tsx
    
    "use client";
    import { useFormStatus } from "react-dom";
    export function Submit() {
      const asdf = useFormStatus();
      console.log(asdf);
      const { pending } = asdf;
      return (
        <Button
          type="submit"
          disabled={pending}
          text={pending ? "Submitting..." : "Submit"}
        />
      );
    }
    //app/../page.tsx
    
    import { Submit } from "./Submit";
    
    type SurveyResultProps = {
      answers: Record<string, Answer>,
    };
    
    const SurveyResult: React.FC<SurveyResultProps> = ({
      answers,
    }: SurveyResultProps) => {
      const [resultText, setResultText] = useState("");
      const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
        event.preventDefault();
        const text = await getResultText(answers);
        setResultText(text);
      };
      return (
        <div>
          <form action={handleSubmit}>
            <Submit />
          </form>
          {resultText && <pre>{resultText}</pre>}
        </div>
      );
    };
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search