reactjsnext.jsserver-side-renderingnext.js13

useFormStatus() is always false in NextJS


I'm trying to use useFormStatus() to change UI dynamically when the form is submitted and it's waiting to fetch the data.

I have a form component like

function onSubmit(data: z.infer<typeof FormSchema>) {
    requestImg(data).then((imageStringify) => {
      const binaryData = Buffer.from(imageStringify.image);
      const imageBase64 = URL.createObjectURL(
        new Blob([binaryData.buffer], { type: "image/jpeg" } /* (1) */)
      );
      setOpen(true);
      setSource(imageBase64);
    });
  }

  if (isDesktop) {
    return (
      <Form {...form}>
        <form
          onSubmit={form.handleSubmit(onSubmit)}
          className="w-2/3 space-y-6"
        >
        ...

with requestImg(data) being a server action.

I've then the Button component nested inside the <form> in a different Button.tsx file

import { useFormStatus } from "react-dom";

import { Button } from "@/components/ui/button";

export default function ButtonSub() {
  const { pending } = useFormStatus();

  console.log(pending);

  return (
    <Button className="w-full" type="submit" aria-disabled={pending}>
      {pending ? "Processing..." : "Submit"}
    </Button>
  );
}

The issue is that, when I click submit, the button text doesn't change to "Processing..."

EDIT

It seems that works only if you're using the action keyword on the form. I've tried

<form
        action={async (formData: FormData) => {
          requestImg(formData).then((imageStringify) => {
            const binaryData = Buffer.from(imageStringify.image);
            const imageBase64 = URL.createObjectURL(
              new Blob([binaryData.buffer], { type: "image/jpeg" } /* (1) */)
            );
            setSource(imageBase64);
            setOpen(true);
          });
        }}
        className="w-2/3 space-y-6"
      >

but now the image generated with the byte8Array is broken. I have an image with a dynamic src filled with

const [source, setSource] = useState("");
<img src={source}/>

Solution

  • A solution I found without using the action keyword but still using server action calling it as a function was to use useTransition()

    import { useState, useTransition } from "react";
    
    const [isPending, startTransition] = useTransition();
    
    function onSubmit(data: z.infer<typeof FormSchema>) {
        startTransition(async () => {
            ... do your things ...
    

    and the Submit Button client component

          <Button
            className="w-full"
            type="submit"
            disabled={isPending}
            aria-disabled={isPending}
          >
            {isPending ? "Processing..." : "Submit"}
          </Button>