T
TanStack•2y ago
deep-jade

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
deep-jade
deep-jadeOP•2y 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
like-gold
like-gold•2y ago
No, you do not. Try adding your {success: true} in your server action, then add a useActionState to get the config
deep-jade
deep-jadeOP•2y 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?
like-gold
like-gold•2y ago
useFormState got renamed to useActionState I need to update the docs and a blog post I wrote
deep-jade
deep-jadeOP•2y ago
I have an error with useActionState, but I think I'm on the latest @next...
like-gold
like-gold•2y ago
Huh, weird. Try the React 19 RC?
deep-jade
deep-jadeOP•2y 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?
like-gold
like-gold•2y 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
deep-jade
deep-jadeOP•2y 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)
deep-jade
deep-jadeOP•2y 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
deep-jade
deep-jadeOP•2y 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 🙂
deep-jade
deep-jadeOP•2y 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.
deep-jade
deep-jadeOP•2y 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>
)
}
like-gold
like-gold•2y 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
deep-jade
deep-jadeOP•2y ago
ooooooh okay, I've seen that issue. Thanks for the heads up

Did you find this page helpful?