T
TanStack3mo ago
ambitious-aqua

How to get data from a backend api validation in the onSubmit?

I'm migrating an app from Conform to Tanstack Form, and i had this kind of validation
const submission = await parseWithZod(formData, {
schema: (intent) =>
schema.transform(async (data, ctx) => {
if (intent !== null) return { ...data, verify: null }

const { data: verify, error } = await api.POST("/api/auth/sign-up", {
body: {
email: data.email,
verify: {
targetQueryParam: targetQueryParam,
typeQueryParam: typeQueryParam,
url: `${import.meta.env.VITE_BASE_URL}${href("/auth/verify")}`,
},
},
})

if (error) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: error.message,
})
return z.NEVER
}

return { ...data, verify }
}),
async: true,
})

if (submission.status !== "success" || !submission.value.verify) {
return submission.reply()
}

const { verify } = submission.value
const submission = await parseWithZod(formData, {
schema: (intent) =>
schema.transform(async (data, ctx) => {
if (intent !== null) return { ...data, verify: null }

const { data: verify, error } = await api.POST("/api/auth/sign-up", {
body: {
email: data.email,
verify: {
targetQueryParam: targetQueryParam,
typeQueryParam: typeQueryParam,
url: `${import.meta.env.VITE_BASE_URL}${href("/auth/verify")}`,
},
},
})

if (error) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: error.message,
})
return z.NEVER
}

return { ...data, verify }
}),
async: true,
})

if (submission.status !== "success" || !submission.value.verify) {
return submission.reply()
}

const { verify } = submission.value
if the api returned an error i was invalidating the form with the api error message, but if there was no error i was populating the form data with the api returned data, so i can use it later, now im trying to do something like that in the ts form, but i cant find a way to do it
3 Replies
ambitious-aqua
ambitious-aquaOP3mo ago
i achieve something similar with this:
const [verify, setVerify] = useState<Verify | undefined>()
const navigate = useNavigate()
const form = useAppForm({
defaultValues: {
email: "",
} as FormSchema,
validators: {
onChange: formSchema,
onSubmitAsync: async ({ signal, value }) => {
const { data: verify, error } = await api.POST("/api/auth/sign-up", {
body: {
email: value.email,
verify: {
targetQueryParam: targetQueryParam,
typeQueryParam: typeQueryParam,
url: `${import.meta.env.VITE_BASE_URL}${href("/auth/verify")}`,
},
},
signal,
})

if (error) {
return error.message
}

setVerify(verify)

return null
},
},
onSubmit: async () => {
if (!verify) {
throw new Error("Should not be able to submit")
}

return navigate(verify.verifyUrl)
},
})
const [verify, setVerify] = useState<Verify | undefined>()
const navigate = useNavigate()
const form = useAppForm({
defaultValues: {
email: "",
} as FormSchema,
validators: {
onChange: formSchema,
onSubmitAsync: async ({ signal, value }) => {
const { data: verify, error } = await api.POST("/api/auth/sign-up", {
body: {
email: value.email,
verify: {
targetQueryParam: targetQueryParam,
typeQueryParam: typeQueryParam,
url: `${import.meta.env.VITE_BASE_URL}${href("/auth/verify")}`,
},
},
signal,
})

if (error) {
return error.message
}

setVerify(verify)

return null
},
},
onSubmit: async () => {
if (!verify) {
throw new Error("Should not be able to submit")
}

return navigate(verify.verifyUrl)
},
})
but this useState just feels wrong, i think i should be able to populate the onSubmit data from the onSubmitAsync validator, am i missing something?
ambitious-aqua
ambitious-aqua3mo ago
onSubmit cannot be reached if onSubmitAsync errored. Therefore, it should be considered an "onSuccess listener" of your form. Now usually, this is good enough, but in your case you want to share data between the two. It's not needed because you can replace your setState action with the actual action of your form:
const [verify, setVerify] = useState<Verify | undefined>()
const navigate = useNavigate()
const form = useAppForm({
defaultValues: {
email: "",
} as FormSchema,
validators: {
onChange: formSchema,
onSubmitAsync: async ({ signal, value }) => {
const { data: verify, error } = await api.POST("/api/auth/sign-up", {
body: {
email: value.email,
verify: {
targetQueryParam: targetQueryParam,
typeQueryParam: typeQueryParam,
url: `${import.meta.env.VITE_BASE_URL}${href("/auth/verify")}`,
},
},
signal,
})

if (error) {
// error.message is not falsy, so this will be considered an error
return error.message
}
// only successful responses reach this part. Make sure
// to not return truthy values here.
await navigate(verify.verifyUrl)
},
},
// onSubmit is not needed as any data manipulation has already been done in the
// onSubmitAsync validator
})
const [verify, setVerify] = useState<Verify | undefined>()
const navigate = useNavigate()
const form = useAppForm({
defaultValues: {
email: "",
} as FormSchema,
validators: {
onChange: formSchema,
onSubmitAsync: async ({ signal, value }) => {
const { data: verify, error } = await api.POST("/api/auth/sign-up", {
body: {
email: value.email,
verify: {
targetQueryParam: targetQueryParam,
typeQueryParam: typeQueryParam,
url: `${import.meta.env.VITE_BASE_URL}${href("/auth/verify")}`,
},
},
signal,
})

if (error) {
// error.message is not falsy, so this will be considered an error
return error.message
}
// only successful responses reach this part. Make sure
// to not return truthy values here.
await navigate(verify.verifyUrl)
},
},
// onSubmit is not needed as any data manipulation has already been done in the
// onSubmitAsync validator
})
ambitious-aqua
ambitious-aquaOP3mo ago
it makes sense, thanks a lot!

Did you find this page helpful?