skip to Main Content

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}/>

2

Answers


  1. Chosen as BEST ANSWER

    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>
    

  2. It seems that works only if you’re using the action keyword on the
    form.

    that is true. useFormStatus has to be used inside a child component of the form and it kinda watches the state change of the server action. But as far as I know it listens for the action. action defines the server-side destination for the form data after submission. in the context of the server action, server actions make a post request to the current URL. so it is not listening for form onSubmit, that is why your first code execution is not working.

    if you read the enter link description here

    const { pending, data, method, action } = useFormStatus();
    

    see the action property, from the docs

    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.

    Now server action runs in server context, they get executed on the server but setState is client specific code. what you have to do is you have to return a value. in your case:

     return imageBase64
    

    and using useFormState you can access to this state. from useFormState docs

    const [state, formAction] = useFormState(action, null);
    

    The current state of the form, which is initially set to the initial
    state you provided, and after the form is submitted is set to the
    return value of the action you provided.

    you should be using this state value inside the component

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search