Next.js Discord

Discord Forum

Bind values to formAction with useFormState

Answered
Baird's Sandpiper posted this in #help-forum
Open in Discord
Baird's SandpiperOP
I have seen examples of using bind on a server action. Is it possible to bind values to the returned formAction from a useFormState( )? Doing so breaks your code

  const [formState, formAction] = useFormState(createTemplate, getEmptyFormState());

  const { toast } = useToast();

  useEffect(() => {
    if (formState.status == "SUCCESS") {
      toast({
        title: "Successfully created system",
        description: `sheesh`,
      });

      redirect("/dashboard/templates");
    } else if (formState.status == "ERROR") {
      toast({
        title: "Something went wrong!",
        description: "Please check the information you are submitting.",
        variant: "destructive",
      });
    }
  }, [formState.status, formState.timestamp]);

  const createWithSessionsAndGoal = createTemplate.bind(null, {
    extra: {
      goal: options.map((o) => o.value),
      sessions: [],
    },
  });
Answered by Ray
try
const [formState, formAction] = useFormState(createTemplate.bind(null, {
   extra: {
      goal: options.map((o) => o.value),
      sessions: [],
    },
})), getEmptyFormState();
View full answer

31 Replies

Baird's SandpiperOP
  const createWithSessionsAndGoal = formAction.bind(null, {
    extra: {
      goal: options.map((o) => o.value),
      sessions: [],
    },
  });
doing so breaks the code when trying to use formAction
Answer
Baird's SandpiperOP
thank you for the insight! What if I wanted those values to be dynamic? Is that still possible? I need to pass into arrays / objects depending on the form and this is kinda painful. Is it just better to create a seperate submit function that calls the server action and appends the needed data?
not liking this whole bind pattern , but its probably meant for simple use cases not like mine
@Ray try ts const [formState, formAction] = useFormState(createTemplate.bind(null, { extra: { goal: options.map((o) => o.value), sessions: [], }, })), getEmptyFormState();
Baird's SandpiperOP
this actually does not work
 Types of parameters 'formData' and 'state' are incompatible.
        Type 'FormState' is not assignable to type 'FormData'.
@Baird's Sandpiper this actually does not work Types of parameters 'formData' and 'state' are incompatible. Type 'FormState' is not assignable to type 'FormData'.
if you bind value to your action, the action should look like this
'use server'
 
export async function createTemplate({extra}, prevState: unknown, formData) {
  // ...
}
@Ray if you bind value to your action, the action should look like this ts 'use server' export async function createTemplate({extra}, prevState: unknown, formData) { // ... }
Baird's SandpiperOP
export const createTemplate: FormServerAction = async (_previousState, formData) => {
  const { user } = await validateRequest();
  if (!user) throw new Error("Unauthorized");

  const result = createTemplateSchema.safeParse({
    ...Object.fromEntries(formData),
  });

  if (!result.success) {
    console.log(fromErrorToFormState(result.error));
    return fromErrorToFormState(result.error);
  }

  try {
    await db.insert(template).values({ ...result.data, summary: "", author: user.id });
  } catch (error) {
    return fromErrorToFormState(error);
  }

  revalidatePath("/dashboard/templates");

  return {
    status: "SUCCESS" as const,
    error: "",
    fieldErrors: {},
    timestamp: Date.now(),
  };
};
export type FormServerAction = (_previousState: unknown, formData: FormData) => Promise<FormState>;
the first argument of the server action always returns the previous state even when i try to bind
Baird's SandpiperOP
 const [formState, formAction] = useFormState(createTemplate, getEmptyFormState());

  const { toast } = useToast();

  useEffect(() => {
    if (formState.status == "SUCCESS") {
      toast({
        title: "Successfully created system",
        description: `sheesh`,
      });

      redirect("/dashboard/templates");
    } else if (formState.status == "ERROR") {
      toast({
        title: "Something went wrong!",
        description: "Please check the information you are submitting.",
        variant: "destructive",
      });
    }
  }, [formState.status, formState.timestamp]);

  return (
    <div className="w-full max-w-xl space-y-4">
      <h2 className="text-2xl font-bold">Create Template</h2>
      <div className="rounded border p-6">
        <form action={formAction} className="space-y-2">
also remember i am using the formAction form the formState and not the serverAction itself
i know
but if you bind value to the action
you need to change the order of the parameters
what do you mean?
Baird's SandpiperOP
the first parameter will be previous form state
not any binding
export async function createTemplate( prevState: unknown, {extra}, formData) {
  // ...
}
try this order
Baird's SandpiperOP
ahh almost
async (bind, _previousState, formData)
thanks for the help this was driving me crazy
u smart
Baird's SandpiperOP
ok maybe i overlooked that one lol my bad
:lolsob:
Australian Freshwater Crocodile
Hey! So did you make this work?

I have a form with the classic type="submit" button but i also want another button for saving and exiting, which should get a special argument somehow, but how can i bind this arg to the action returned by useActionState (previously called useFormState)?
Australian Freshwater Crocodile
Ended up doing it with both buttons type="submit" and having the same name but different values and just getting the option from the formData 👍🏽