Playwright, `useActionState`, and bound server functions

Quick one that has a fix but no explanation for why the problem is there (yet).

I have a server action:

'use server';

export async function serverAction(param1) {
  const newState = doThings(param1);

  return newState;
}

I also have form that uses useActionState to handle the form submission:

const boundServerAction = serverAction.bind(  
  null,  
  param1
);  
const [state, formAction] = useActionState(boundServerAction, null);

return (
  <form action={formAction}>
    <button type="submit">Submit</button>
  </form>
);

This works in the browser, but in Playwright state is never updated. An unbound function works fine.

This makes it seem as though it's to do with the param binding, possibly because prevState: unknown, formData: FormData are added to the server action params. But in this case they're not used, so should it matter? What is Playwright doing in instrumentation that stops this from working? I'm not sure and haven't had time to investigate.

But there are two workarounds here:

Options 1. Wrap the server action

Simplest solution: Just don't bind the server action.

const [state, formAction] = useActionState(() => serverAction(param1), null);

return (
  <form action={formAction}>
    <button type="submit">Submit</button>
  </form>
);

Option 2. Use form inputs

To be clear, I don't like this. I shouldn't need to expose param1. But this works and may suit your use case for some reason.

Update the server action to use formData:

'use server';

export async function serverAction(prevState, formData) {
  const newState = doThings(formData.get('param1'));

  return newState;
}

Update your form to add param1 as a hidden value.

const [state, formAction] = useActionState(serverAction, null);

return (
  <form action={formAction}>
    <input type="hidden" name="param1" value={param1} />
    <button type="submit">Submit</button>
  </form>
);
Published February 19, 2025