TheEdoRan / next-safe-action

Type safe and validated Server Actions in your Next.js (App Router) project.
https://next-safe-action.dev
MIT License
1.55k stars 27 forks source link

[BUG] Sending form with javascript disabled not working #189

Open palmjack opened 4 days ago

palmjack commented 4 days ago

Are you using the latest version of this library?

Is there an existing issue for this?

Describe the bug

Sending form with js disabled does not work when using useAction hook.

Reproduction steps

  1. Go to playground https://next-safe-action-playground.vercel.app/stateless-form
  2. Disable javascript in devtools
  3. Try sending the form

See attached screenshot of the form element CleanShot 2024-07-02 at 12 48 01@2x

Expected behavior

Form should be sent with javascript disabled, at least that's what I understand from looking at the example in the docs https://next-safe-action.dev/docs/recipes/form-actions

Minimal reproduction example

https://next-safe-action-playground.vercel.app/stateless-form

Operating System

macOS

Library version

7.1.3

Next.js version

15.0.0-rc.0

Additional context

No response

TheEdoRan commented 3 days ago

Not sure if I'm missing something, but how can it work with JS disabled if you're using the action via a hook, which is a JavaScript function by definition? Don't think this is related to next-safe-action. I guess progressive enhancement is only available if you're passing the Server Action directly to the form action.

palmjack commented 3 days ago

You may want to take a look on Jack's Harrington example repo and its corresponding yt video I also checked ZSA and its executeFormAction method from useServerAction hook and it works as expected with js disabled. See the example

tenghuan123 commented 2 days ago

First, I tested the executeFormAction exported by zsa's useServerAction, which also does not support progressive enhancement. If I write it like this, I will still get an unsupported error prompt, just like this:

export function UseZsaStateExaple() {
  const { isPending, executeFormAction, data, error } =
    useServerAction(produceNewMessage);

  return (
    <div>
      <form action={executeFormAction}>
        <input name="name" placeholder="Enter your name..." />
        <button type="submit" disabled={isPending}>
          submit me
        </button>
      </form>
      {isPending && <div>Loading...</div>}
      {data && <p>Message: {data}</p>}
      {error && <div>Error: {JSON.stringify(error)}</div>}
    </div>
  );
}

image

In addition, if you want to support progressive enhancement, you need to use the official useActionState of React, and the example of zsa is the same

"use client";

import { useActionState } from "react";
import { produceNewMessage } from "./actions";

export default function UseActionStateExample() {
  let [[data, err], submitAction, isPending] = useActionState(
    produceNewMessage,
    [null, null] // or [initialData, null]
  );

  return (
    <div>
      <form action={submitAction}>
        <input name="name" placeholder="Enter your name..." />
        <button type="submit" disabled={isPending}>
          submit me
        </button>
      </form>
      {isPending && <div>Loading...</div>}
      {data && <p>Message: {data}</p>}
      {error && <div>Error: {JSON.stringify(error)}</div>}
    </div>
  );
}
tenghuan123 commented 2 days ago

Here is an example of using useActionState to support progressive enhancement in next-safe-action

// actions.ts
'use server'

import { createSafeActionClient } from "next-safe-action"
import { zfd } from "zod-form-data";

export const produceNewMessageAction = createSafeActionClient()
  .schema(
    zfd.formData({
      name: zfd.text(),
    })
  )
  .stateAction(async ({ parsedInput }) => {
    await new Promise((resolve) => { setTimeout(resolve, 500) })
    return "Hello, " + parsedInput.name
  })
"use client"

import { useActionState } from "react";
import { produceNewMessageAction } from "./actions";

export function UseActionStateExample() {
  const [state, submitAction, isPending] = useActionState(
    produceNewMessageAction,
    {}
  );

  return (
    <div>
      <form action={submitAction}>
        <input name="name" placeholder="Enter your name..." />
        <button type="submit" disabled={isPending}>submit me</button>
      </form>
      {isPending && <div>Loading...</div>}
      {state.data && <p>Message: {state.data}</p>}
      {state.validationErrors && <div>Error: {JSON.stringify(state.validationErrors)}</div>}
    </div>
  );
}
palmjack commented 2 days ago

Thanks for making it clear. I think it could be a good idea to clearly state it in the docs that if you want progressive enhancement you need to use react native hook.

TheEdoRan commented 5 hours ago

As @tenghuan123 said, progressive enhancement is available via useActionState hook from React.

next-safe-action exports a hook called useStateAction from /stateful-hooks path, but I've just tested it and it doesn't work with JavaScript disabled. The execute function returned from that hook is a wrapper of the dispatcher from React's useActionState hook. This is because otherwise useful props (such as status and client input) couldn't be returned from the hook — see the image below. *

Now I'm actually thinking of changing the useStateAction API to just return the [state, action, isPending] original array or remove the stateful hook entirely from the library and tell users to use the React one. If this will be the case, the useStateAction hook will be marked as deprecated for a number of minor versions and then removed.

What do you think?

* useStateAction return object: useStateAction return object