Skip to content

[BUG] useAction.inputissue with zod-form-data #322

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
2 tasks done
armandabric opened this issue Feb 12, 2025 · 8 comments
Open
2 tasks done

[BUG] useAction.inputissue with zod-form-data #322

armandabric opened this issue Feb 12, 2025 · 8 comments
Labels
bug Something isn't working

Comments

@armandabric
Copy link

armandabric commented Feb 12, 2025

Are you using the latest version of this library?

  • I verified that the issue exists in the latest next-safe-action release

Is there an existing issue for this?

  • I have searched the existing issues and found nothing that matches

Describe the bug

When using next-safe-action with zod-safe-action schema, the useAction(...).input type is not inferred correctly.

In the action body the input type is ok:

const schema = zfd.formData({
  bar: zfd.text(z.string().min(3).max(10)),
});

export const doSomethingWithZodFormData = actionClient
  .schema(schema)
  .action(async ({ parsedInput: { bar } }) => {
    // Here the `bar` input is a `string` as expected
    const baz: string = bar;

    return { /* ... */ };
  });

The issue is only with the hook:

const doSomethingWithZodFormActionAction = useAction(doSomethingWithZodFormData)

// With a schema made with zod-safe-action, the input type is not ok: `FormData | FormDataLikeInput`
const bar = doSomethingWithZodFormActionAction.input?.bar

The issue also present with the useAction(...).execute function.

Everything works correctly when using a zod schema.

Reproduction steps

In the linked reproduction sandbox, if you open the app/page.tsx file you will see two useAction usage: one with a zod schema, the other with a zod-safe-action schema. The second case trigger a TS error.

Expected behavior

As the input type is well inferred in the action, I'm expecting it to the be inferred when using the useAction hook.

Link to a minimal reproduction of the issue

https://codesandbox.io/p/devbox/epic-torvalds-vt88n4

Library version

7.10.3

Next.js version

15.1.7

Node.js version

20.9.0

Additional context

No response

@armandabric armandabric added the bug Something isn't working label Feb 12, 2025
@chungweileong94
Copy link

TLDR;
It's zod-form-data that need to fix its input type for zfd.formData().

It's because zfd.formData() is a Zod effect, where the input type will be inferred as FormData | FormDataLikeInput (https://github.com/airjp73/rvf/blob/d7f7e983f43c5938d48f99dd744827ce9e559b34/packages/zod-form-data/src/helpers.ts#L128), even though you can technically pass in an object thanks to zod-form-data (https://github.com/airjp73/rvf/blob/d7f7e983f43c5938d48f99dd744827ce9e559b34/packages/zod-form-data/src/helpers.ts#L194), but it might not always the case for other Zod effect use cases.

I was once supporting this in my server action library chungweileong94/server-act#35, but I found myself fighting against the types for this specific use-case (zod-form-data), so I ended up ditching it when I adopted standard schema chungweileong94/server-act#37.

@chungweileong94
Copy link

chungweileong94 commented Mar 12, 2025

This should be fixed in the latest zod-form-data.

@TheEdoRan
Copy link
Owner

@chungweileong94 thanks for fixing this issue in zod-form-data!
@armandabric can you confirm that everything is working properly now?

@armandabric
Copy link
Author

armandabric commented Mar 26, 2025

I just try again with [email protected], and the input object appear now:

Image

But it's hardly usable with FormData & FormDataLikeInput in the union: we have exclude them before being able to use any attribute in the input.

Property 'mode' does not exist on type 'FormData | FormDataLikeInput | { mode: "group" | "split"; locale: "en" | "de" | "es" | "fr" | "nl" | "pt" | "it"; }'.
  Property 'mode' does not exist on type 'FormData'.ts(2339)

@chungweileong94
Copy link

chungweileong94 commented Mar 26, 2025

But it's hardly usable yet with FormData & FormDataLikeInput in the union: we have exclude them before being able to use any attribute in the input.

Property 'mode' does not exist on type 'FormData | FormDataLikeInput | { mode: "group" | "split"; locale: "en" | "de" | "es" | "fr" | "nl" | "pt" | "it"; }'.
  Property 'mode' does not exist on type 'FormData'.ts(2339)

Ah, I see, but I don't think it's considered a bug in either zod-form-data or next-safe-action.
Fortunately, due to the fact that it can be both FormData or an object, you can perform a check (aka type guard) before accessing the object property.

if (!("entries" in selectDeliveryModeAction.input)) {
  // The input is object here
  console.log(selectDeliveryModeAction.input.mode);
}

You can also use this helper function to check if the input is a form data

type FormDataLikeInput = {
  [Symbol.iterator](): IterableIterator<[string, FormDataEntryValue]>;
  entries(): IterableIterator<[string, FormDataEntryValue]>;
};

function isFormDataLikeInput<T>(
  input: FormData | FormDataLikeInput | T,
): input is FormData | FormDataLikeInput {
  return "entries" in input;
}

...

if (!isFormDataLikeInput(selectDeliveryModeAction.input)) {
  console.log(selectDeliveryModeAction.input.mode);
}

@chungweileong94
Copy link

I'm actually made a mistake on the helper function. This is the correct version.

function isFormDataLikeInput<T extends Record<string, unknown>>(
  input: FormData | FormDataLikeInput | T,
): input is FormData | FormDataLikeInput {
  return "entries" in input;
}

@adamlindqvist
Copy link

adamlindqvist commented Apr 7, 2025

I just try again with [email protected], and the input object appear now:

Image

But it's hardly usable with FormData & FormDataLikeInput in the union: we have exclude them before being able to use any attribute in the input.

Property 'mode' does not exist on type 'FormData | FormDataLikeInput | { mode: "group" | "split"; locale: "en" | "de" | "es" | "fr" | "nl" | "pt" | "it"; }'.
  Property 'mode' does not exist on type 'FormData'.ts(2339)

I agree. I'd say it impossible to use input since the types are very confusing.

Edit: To add some context of that I'm trying to achive

My expectation is to use the input to set the defaultValue of a input e.g. if there is a server/custom error in the action:

const { execute, input } = useAction(loginFormAction);

return (
    <form className={styles.Form} action={execute}>
       <input type="text" defaultValue={input.name} /> 
    </form>
)

I cant find any way to accomplish this at the moment.

@milad-mamandi
Copy link

any updates regarding this issue? i also cannot use input

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

5 participants