Can't make Work properly a server action from a client form
Unanswered
Rex posted this in #help-forum
RexOP
Hi all!
i'm losing my mind on such an easy thing.
i've also done a TodoApp just for test and i can't make a form action called from a client form work.
the code is pretty basic, i'm also using an in memory array. I've also created a server side form and works as expected, but not the client one.
here is the code for the client form:
and this is the server action, in a separate file of course as stated in the docs.
Here the crazy part ( for me, maybe i'm missing something):
if i call that action from a server-side form it work, and than start working even the client-side form
if i create a separate "inline" action for the server form, this will work fine but the action called from the client form ( the one here in the code, in his separate file ) will NOT work
any help will be very appreciated, this is driving me crazy
thanks
i'm losing my mind on such an easy thing.
i've also done a TodoApp just for test and i can't make a form action called from a client form work.
the code is pretty basic, i'm also using an in memory array. I've also created a server side form and works as expected, but not the client one.
here is the code for the client form:
function FormClient( {} : Props ){
const {pending} = useFormStatus()
const ref = useRef<HTMLFormElement>(null)
return (
<form ref={ref} action={ async formData => {
await pushTodo(formData)
ref.current?.reset();
}} className="flex gap-y-3 flex-col">
<label htmlFor="todo">{'Action from client'}</label>
<input className="text-black" type="text" id="todo" name="todo" required />
<button className="p-2 bg-teal-900 rounded-lg" type="submit" aria-disabled={pending} disabled={pending}>
{'Add Todo'}
</button>
</form>
);
}
and this is the server action, in a separate file of course as stated in the docs.
export const pushTodo = async (data: FormData) => {
const d = data.get("todo") as string
await new Promise( resolve =>
setTimeout(() => {
resolve(todos.push(d))
}, 500))
console.log('calling addTodo with: ', d)
revalidatePath("/")
return {message: "done"}
}
Here the crazy part ( for me, maybe i'm missing something):
if i call that action from a server-side form it work, and than start working even the client-side form
if i create a separate "inline" action for the server form, this will work fine but the action called from the client form ( the one here in the code, in his separate file ) will NOT work
any help will be very appreciated, this is driving me crazy
thanks
14 Replies
dont worry man, we've all been there. Did you forget the
'use server'; part or just omit it?And the
use client alsoRexOP
Sorry maybe was not copied, I’ve double and triple checked, in the client form I’ve set the “use client†as well as in the action the “use server†( both at the top of the file)
The weird behaviour is that when I click the button it actually call the action, I’ve added some console.log in the action, but the revalidatePath is not triggered somehow.
Using the Network tab I can see the post but in the payload is missing the actionId.
( while using the server side form the actionId is there as expected in the request payload).
The weird behaviour is that when I click the button it actually call the action, I’ve added some console.log in the action, but the revalidatePath is not triggered somehow.
Using the Network tab I can see the post but in the payload is missing the actionId.
( while using the server side form the actionId is there as expected in the request payload).
RexOP
Update:
If I pass the action as prop from a server component to the client component it works!
But in a real case scenario I don’t like to pro drill a function from server to client component.
I can’t find in the docs that this is the intended practice..
Am I Wrong ?
If I pass the action as prop from a server component to the client component it works!
But in a real case scenario I don’t like to pro drill a function from server to client component.
I can’t find in the docs that this is the intended practice..
Am I Wrong ?
I dont understand what's going on. I'm going to share with you a simple form component I have that opens in a dialog (uploaded file).
Here some important snippets
Here some important snippets
'use client'
import { FormSubmit } from '@/components/form/form-submit'
import { saveOrUpdate } from '@/actions/companies'
export default function CompanyForm ({ company, children }: CompanyFormProps) {
// Same as you, but I extracted in a const so the HTMX looks more compact
const onSubmit = async (id: string | null, formData: FormData) => {
const name = formData.get('name') as string
const response = await saveOrUpdate({ name }, id)
let errors: string[] = []
if (response?.type === 'error' && response?.message) {
errors.push(response.message)
setErrors(errors)
}
if (response?.type === 'success') {
toaster.send(response)
closeRef.current?.click()
}
}
const submitFn = company
? onSubmit.bind(null, company.id)
: onSubmit.bind(null, null)
return (
<form action={submitFn}>
<FormErrors errors={errors} />
<div className='flex flex-col gap-3'>
<Input
name='name'
placeholder='Nome da empresa'
defaultValue={company?.name}
/>
</div>
// other stuff
<DialogFooter className='mt-24 flex flex-row gap-1'>
<DialogClose
ref={closeRef}
asChild
onClick={() => setErrors(undefined)}
>
<Button variant='destructive' size='lg'>
Cancelar
</Button>
</DialogClose>
<FormSubmit size='lg'>Salvar</FormSubmit>
</DialogFooter>
</form>Oh so the problem is that your revalidatePath is not working right?
This is your code:
Please try this:
await new Promise( resolve =>
setTimeout(() => {
resolve(todos.push(d))
}, 500))
console.log('calling addTodo with: ', d)
revalidatePath("/")
return {message: "done"}Please try this:
Move revalidatePath("/") inside your timeout after 500ms.
My guess is that you're revalidating the path before you actually add the data on the
My guess is that you're revalidating the path before you actually add the data on the
todos list.RexOP
thanks for sharing your thoughts, however i've checked again, moved the revalidatePath just after the call to fetch, but it doesn't work..
@Rex Update:
If I pass the action as prop from a server component to the client component it works!
But in a real case scenario I don’t like to pro drill a function from server to client component.
I can’t find in the docs that this is the intended practice..
Am I Wrong ?
we can pass the action as props to client component. Sometime I had to do this if the action use some external packages otherwise I got webpack error
Bighead carp
@Rex maybe show the side-by-side codeblocks of what does work and what doesnt. specifically what the change is that stops things from working
and make sure wrap your code with three of these characters: `
const example = 2;