T
TanStack•16mo ago
extended-yellow

How can I show success on client using server action?

I am trying to useForm method. I want to show some nice UI when the form submits correctly. I am using NextJS and server actions. In my useForm config, I am trying to do this:
onSubmit: async (test) => {
console.log('T', test)
if (!state.success) {
enqueueSnackbar('Failed to create manual application.', {
variant: 'error',
})
return
}
enqueueSnackbar('Successfully created manual application.', {
variant: 'success',
})
setOpen(false)
},
onSubmit: async (test) => {
console.log('T', test)
if (!state.success) {
enqueueSnackbar('Failed to create manual application.', {
variant: 'error',
})
return
}
enqueueSnackbar('Successfully created manual application.', {
variant: 'success',
})
setOpen(false)
},
But the state is old, so I think I'm looking at the wrong object? My server action for example would return {success: true/false}
15 Replies
extended-yellow
extended-yellowOP•16mo ago
omg, do I just have to have a useEffect with state as a dependency? seems to work fine, but would be nice if there was a way I can do it in the config of useForm
conscious-sapphire
conscious-sapphire•16mo ago
No, you do not. Try adding your {success: true} in your server action, then add a useActionState to get the config
extended-yellow
extended-yellowOP•16mo ago
@crutchcorn hmmm I thought I tried that, but ill check it out again. Thanks is it useFormState or useActionState? Can you show me an example of how you would implement that in the onSubmit function?
conscious-sapphire
conscious-sapphire•16mo ago
useFormState got renamed to useActionState I need to update the docs and a blog post I wrote
extended-yellow
extended-yellowOP•16mo ago
I have an error with useActionState, but I think I'm on the latest @next...
conscious-sapphire
conscious-sapphire•16mo ago
Huh, weird. Try the React 19 RC?
extended-yellow
extended-yellowOP•16mo ago
TypeError: (0 , react__WEBPACK_IMPORTED_MODULE_5__.useActionState) is not a function or its return value is not iterable
TypeError: (0 , react__WEBPACK_IMPORTED_MODULE_5__.useActionState) is not a function or its return value is not iterable
regardless, if I figure that out, is it just as simple as doing this?
const [state, action] = useActionState(
createManualApplication,
formFactory.initialFormState
)

const { Subscribe, handleSubmit, Field } = useForm<{
businessName: string
emailAddress: string
phoneNumber: string
applicationFile: File | null
}>({
defaultValues: {
businessName: '',
emailAddress: '',
phoneNumber: '',
applicationFile: null,
},
transform: useTransform(
(baseForm: FormApi<any, any>) => mergeForm(baseForm, state),
[state]
),
onSubmit: async (values) => {
console.log('S', state)
},
})
const [state, action] = useActionState(
createManualApplication,
formFactory.initialFormState
)

const { Subscribe, handleSubmit, Field } = useForm<{
businessName: string
emailAddress: string
phoneNumber: string
applicationFile: File | null
}>({
defaultValues: {
businessName: '',
emailAddress: '',
phoneNumber: '',
applicationFile: null,
},
transform: useTransform(
(baseForm: FormApi<any, any>) => mergeForm(baseForm, state),
[state]
),
onSubmit: async (values) => {
console.log('S', state)
},
})
I just look at the state var from the hook?
conscious-sapphire
conscious-sapphire•16mo ago
You might wanna add an if in the mergeForm To conditionally handle the merge from the server in case it returns non form data But otherwise yeah
extended-yellow
extended-yellowOP•16mo ago
I think I have to review the mergeForm stuff, I mostly just ripped that from the example Is TSForm still a rather new product from TS? Also, I think useActionState is only available in the newest react, so I'll probably still try and useFormState. Do you think the same thing would fix there? I originally had that, but the state in the onSubmit was One behind (the first submit it would have initial data, second submit would have the response from the server action)
extended-yellow
extended-yellowOP•16mo ago
yeah so this:
transform: useTransform(
(baseForm: FormApi<any, any>) => {
console.log('Transform', baseForm)
console.log('useTransform state:', state)
return mergeForm(baseForm, state)
},
[state]
),
onSubmit: async (values) => {
console.log('onSubmit', state)
},
transform: useTransform(
(baseForm: FormApi<any, any>) => {
console.log('Transform', baseForm)
console.log('useTransform state:', state)
return mergeForm(baseForm, state)
},
[state]
),
onSubmit: async (values) => {
console.log('onSubmit', state)
},
has this console output when I submit
No description
extended-yellow
extended-yellowOP•16mo ago
so the onSubmit is still old, but the transformState is correct, is the merge incorrect? @crutchcorn hate to ping direct, but would love to put a pin in this! I would be open to making relevnat suggestions to documentation 🙂
extended-yellow
extended-yellowOP•16mo ago
@crutchcorn to make my example a bit more verbose, I am using the same code from the TanStack Form documentation, with the server validation of age. Let me know how I can change the code to have an updated state, without needing a useEffect.
extended-yellow
extended-yellowOP•16mo ago
code:
import Client, { formFactory } from './client'

export async function myAction(prev: unknown, formData: FormData) {
return await formFactory.validateFormData(formData)
}

export default async function Page() {
return (
<div className="container mx-auto py-10">
<Client />
</div>
)
}
import Client, { formFactory } from './client'

export async function myAction(prev: unknown, formData: FormData) {
return await formFactory.validateFormData(formData)
}

export default async function Page() {
return (
<div className="container mx-auto py-10">
<Client />
</div>
)
}
'use client'

import {
FormApi,
createFormFactory,
mergeForm,
useTransform,
} from '@tanstack/react-form'
import { useFormState } from 'react-dom'
import { myAction } from './page'
import { useEffect } from 'react'

export const formFactory = createFormFactory({
defaultValues: {
firstName: '',
age: 0,
},
onServerValidate({ value }) {
if (value.age < 12) {
return 'Server validation: You must be at least 12 to sign up'
}
},
})

export default function Client() {
const [state, action] = useFormState(myAction, formFactory.initialFormState)

const { useStore, Subscribe, handleSubmit, Field } = formFactory.useForm({
transform: useTransform(
(baseForm: FormApi<any, any>) => mergeForm(baseForm, state),
[state]
),
onSubmit: async (values) => {
console.log('On submit state', state)
},
})

useEffect(() => {
console.log('Use Effect state', state)
}, [state])

const formErrors = useStore((formState) => formState.errors)

return (
<form action={action as never} onSubmit={() => handleSubmit()}>
{formErrors.map((error) => (
<p key={error as string}>{error}</p>
))}

...
</form>
)
}
'use client'

import {
FormApi,
createFormFactory,
mergeForm,
useTransform,
} from '@tanstack/react-form'
import { useFormState } from 'react-dom'
import { myAction } from './page'
import { useEffect } from 'react'

export const formFactory = createFormFactory({
defaultValues: {
firstName: '',
age: 0,
},
onServerValidate({ value }) {
if (value.age < 12) {
return 'Server validation: You must be at least 12 to sign up'
}
},
})

export default function Client() {
const [state, action] = useFormState(myAction, formFactory.initialFormState)

const { useStore, Subscribe, handleSubmit, Field } = formFactory.useForm({
transform: useTransform(
(baseForm: FormApi<any, any>) => mergeForm(baseForm, state),
[state]
),
onSubmit: async (values) => {
console.log('On submit state', state)
},
})

useEffect(() => {
console.log('Use Effect state', state)
}, [state])

const formErrors = useStore((formState) => formState.errors)

return (
<form action={action as never} onSubmit={() => handleSubmit()}>
{formErrors.map((error) => (
<p key={error as string}>{error}</p>
))}

...
</form>
)
}
conscious-sapphire
conscious-sapphire•16mo ago
@Tyler hold off on our SSR usage. It leaks server code to the client, which was news to me. We'll need to revisit the API some other time
extended-yellow
extended-yellowOP•16mo ago
ooooooh okay, I've seen that issue. Thanks for the heads up

Did you find this page helpful?