T
TanStack6mo ago
automatic-azure

Tanstack Form + Start with isSubmitting handling?

I'm trying to use Tanstack Form + Start together. I can get it mostly working. But, I never see isSubmitting as true, so the UI doesn't show the pending state. The example at https://github.com/TanStack/form/tree/main/examples/react/tanstack-start has this problem, as does the integration guide for Start + Form at https://tanstack.com/form/latest/docs/framework/react/guides/ssr I think it's because they both use
<form action={handleForm.url} method="post" encType={'multipart/form-data'}>
<form action={handleForm.url} method="post" encType={'multipart/form-data'}>
, instead of handling submit themselves. In fact, if the submission succeeds, the example navigates to http://localhost:3000/_server/app_utils_form_tsx--handleForm_createServerFn_handler and the user is left with just "Form has validated successfully" on the screen. Is that intended?? I can change my form submission to handleSubmit() but I'm not sure how to handle this end-to-end. I believe: * I'm not submitting form data anymore, * I can't use createServerValidate() I'm not sure if this is the right path, and how it affects other things like the useStore and useLoaderData. Could you give me a little guidance and ideally adjust the example to work fully with pending state?
7 Replies
narrow-beige
narrow-beige6mo ago
I'm having the same problem with isSubmitting. Were you able to find a solution? If not you should open a ticket.
automatic-azure
automatic-azureOP6mo ago
My recipe so far is to treat isSubmitted || isSubmitting || !isValid as the loading state to disable the submit button. It's possible I should be using canSubmit but it sounds like that has problems too. I also had to prevent submission in case the form isn't valid like so:
<form action={handleForm.url}
onSubmit={(e) => {
if (!isValid) {
e.preventDefault()
return
}
void form.handleSubmit()
}}
>
<form action={handleForm.url}
onSubmit={(e) => {
if (!isValid) {
e.preventDefault()
return
}
void form.handleSubmit()
}}
>
Now this form works with or without Javascript, and the server still gets form-data. It would be helpful to get a working example from the tanstack team. There are a lot of moving parts. I'm still messing around with it.
narrow-beige
narrow-beige6mo ago
In your original post you mentioned that isSubmitting isn't working but in that example you just gave, you're using it. Did you learn something?
automatic-azure
automatic-azureOP6mo ago
Yes there were several entangled issues. Let me see if I can write this accurately, i've learned a bunch: 1) When I wasn't getting the isSubmitting, the issue was this: There's a cookie from the server, which (correctly) showed my form state as invalid. But the form would still submit (it's just a DOM component with an action URL after all). The if (!isValid) e.preventDefault() above prevents that case. At the time I didn't even realize there was a cookie messing with my experiment so I was very confused. 2) isSubmitting is only true for a very short interval even when I add an intentional server delay to emulate server processing. isSubmitted remains true, so I added that the disable condition. (I've since seen several examples with canSubmit, which may be the right thing to guard submission on, but I haven't tried it) I slowly worked it out by deleting the cookie and watching the behavior of all the flags. Now I'm dealing with the fact that error handling is different for a client-side zod validator vs server-side zod validator. The client side supports field errors, but the server side doesn't and requires extra filtering I think. The tanstack-start integration feels a little unfinished, but it's also small and easy to look at the source code to see what it's doing. @html_extraordinaire it sounds like you're dealing with similar stuff, if you want to compare notes more we can DM
exotic-emerald
exotic-emerald6mo ago
Hey @NilsB Can you share the working code? I also trying to replicate the default Tanstack Form + Start example and no far no luck.
automatic-azure
automatic-azureOP6mo ago
I'm still messing with it. I can share a few snippets of changes relative to the start+form example from their repo. 1) change the form action handler:
<form action={handleForm.url}
onSubmit={(e) => {
if (!isValid) {
e.preventDefault()
return
}
void form.handleSubmit()
}}
>
<form action={handleForm.url}
onSubmit={(e) => {
if (!isValid) {
e.preventDefault()
return
}
void form.handleSubmit()
}}
>
2) In the server function, use a raw "Response" for a redirect or any other response besides a server validation error:
throw new Response("ok", {
status: 302,
headers: {
Location: "/",
},
})
throw new Response("ok", {
status: 302,
headers: {
Location: "/",
},
})
3) EDIT: this step no longer needed as of tanstack-forms v1.2.2. I think I should clear the _tanstack_form_internals cookie in almost all cases in the server function. 4) in the submit button, make button unsubmittable while another submit is in progress. Currently I have this but I think it may be possible to use canSubmit instead:
function SubscribeButton({ label }: { label: string }) {
const form = useFormContext()
return (
<form.Subscribe
selector={(state) => {
return {
isSubmitting: state.isSubmitting,
isSubmitted: state.isSubmitted,
isValid: state.isValid,
}
}}>
{({ isSubmitting, isSubmitted, isValid }) => (
<Button type="submit" disabled={isSubmitting || isSubmitted || !isValid}>
{label}
</Button>
)}
</form.Subscribe>
)
}
function SubscribeButton({ label }: { label: string }) {
const form = useFormContext()
return (
<form.Subscribe
selector={(state) => {
return {
isSubmitting: state.isSubmitting,
isSubmitted: state.isSubmitted,
isValid: state.isValid,
}
}}>
{({ isSubmitting, isSubmitted, isValid }) => (
<Button type="submit" disabled={isSubmitting || isSubmitted || !isValid}>
{label}
</Button>
)}
</form.Subscribe>
)
}
5) Workaround for server-side validation errors: In server validation, Zod field errors dont' show up in the fields on the form. Apparently this is something the Forms team is fixing, so I put a workaround in place:
const temp__serverFieldErrors = useStore(
form.store,
(formState) =>
(formState.errorMap as unknown as ServerResponse["errorMap"]).onServer?.fields,
)
// temporarily display server-side field level errors on form until
// https://github.com/TanStack/form/issues/1260 is addressed.
const temp__RenderedFieldErrors = Object.keys(temp__serverFieldErrors || {}).map(
(fieldName, idx) =>
// each field has an array of zod issues. display each issue.
temp__serverFieldErrors?.[fieldName]?.map((zodIssue) => (
<li key={fieldName + idx.toString()}>
{fieldName}: {zodIssue.message}
</li>
)),
)
const temp__serverFieldErrors = useStore(
form.store,
(formState) =>
(formState.errorMap as unknown as ServerResponse["errorMap"]).onServer?.fields,
)
// temporarily display server-side field level errors on form until
// https://github.com/TanStack/form/issues/1260 is addressed.
const temp__RenderedFieldErrors = Object.keys(temp__serverFieldErrors || {}).map(
(fieldName, idx) =>
// each field has an array of zod issues. display each issue.
temp__serverFieldErrors?.[fieldName]?.map((zodIssue) => (
<li key={fieldName + idx.toString()}>
{fieldName}: {zodIssue.message}
</li>
)),
)
I haven't gotten any feedback from the Tanstack team on whether there's a better way, this is what I've picked up so far. I don't think I'm done yet either.
exotic-emerald
exotic-emerald6mo ago
Than You @NilsB Will try out your suggestions.

Did you find this page helpful?