Future Export: the useForm
hook
#1014
Replies: 10 comments 60 replies
-
does this new |
Beta Was this translation helpful? Give feedback.
-
I see that |
Beta Was this translation helpful? Give feedback.
-
Whenever I add a file upload input to the page and try to submit the form (to a react-router action), Chrome crashes with a "Error code: RESULT_CODE_KILLED_BAD_MESSAGE". Have you tested with file uploads? I'm not 100% sure what is happening, but if I submit without going through the Conform |
Beta Was this translation helpful? Give feedback.
-
Question about Hi, thanks for the new future API, I’m trying it out and noticed something I don’t fully understand. When I use the current API, the Here’s a minimal example comparing the two: import { getFormProps, useForm } from '@conform-to/react';
import { useForm as future_useForm } from '@conform-to/react/future';
import { unstable_coerceFormValue as coerceFormValue, parseWithValibot } from '@conform-to/valibot';
import * as v from 'valibot';
const schema = v.object({
index: v.pipe(v.number(), v.minValue(0), v.maxValue(100)),
});
export function FormWithCurrentAPI() {
const [form, fields] = useForm({
shouldValidate: "onInput",
shouldRevalidate: "onInput",
onValidate: ({ formData }) => {
return parseWithValibot(formData, { schema });
}
})
return (
<form {...getFormProps(form)}>
<div>Current API</div>
<div>{form.valid ? "valid" : "invalid"}</div>
<div>
<label htmlFor={fields.index.id}>Index</label>
<input
id={fields.index.id}
name={fields.index.name}
defaultValue={fields.index.defaultValue}
/>
<div>{fields.index.errors}</div>
</div>
</form>
)
}
export function FormWithFutureAPI() {
const { form, fields } = future_useForm({
shouldValidate: "onInput",
shouldRevalidate: "onInput",
schema: coerceFormValue(schema),
});
return (
<form {...form.props}>
<div>Future API</div>
<div>{form.valid ? "valid" : "invalid"}</div>
<div>
<label htmlFor={fields.index.id}>Index</label>
<input
id={fields.index.id}
name={fields.index.name}
defaultValue={fields.index.defaultValue}
/>
<div>{fields.index.errors}</div>
</div>
</form>
)
} Steps to reproduce
Question Is this the intended behavior of the future API? Thanks a lot! |
Beta Was this translation helpful? Give feedback.
-
I notice that server actions feel a little complex in this new version. It's nice to be able to pass in a standard schema property to the It would be nice to have a similar shortcut on the server side such as It's nice to have the granular control over everything, but we lack that same control on the client side. This came up because I was trying to integrate the HeadlessUI 2.0 ComboBox without relying on the |
Beta Was this translation helpful? Give feedback.
-
The new future exports sound great, seems like some corners have been straightened out (or removed) 👍🏻 Do I understand correctly though, that the only way to infer the FormShape is by passing a standard-schema So if I do not want to rely on schema for some reason, I have to pass the FormShape type parameter manually, correct? (Click here for Context)I'm still not happy with Zod's i18n support, even though it has improved with v4. Therefore, I need to pass my own i18n errormap to the parse function, which is not possible via Zod's standard-schema implementation. So I have to either wrap that, or do the parsing in onValidate and pass the inferred type myself. Or find another way to get happy with Zod i18n :) |
Beta Was this translation helpful? Give feedback.
-
Hi, I saw PR #1041 , which adds support for passing schema Do you think the same improvement could also apply to the // current
onValidate: ({ payload }) => {
const result = schema.safeParse(payload);
const { value, error } = formatResult(result, { includeValue: true });
if (value) setState(value);
return error;
},
// proposed
onValidate: ({ payload }) => {
const result = schema.safeParse(payload);
if (result.success) {
setState(result.data);
return null;
}
return { issues: result.error.issues };
}, This would:
Would you consider extending the same support to |
Beta Was this translation helpful? Give feedback.
-
I find myself tripping over the missing DefaultMetadata export often when trying to write some abstractions for my forms. For example, I want to write a Field component that takes a FieldMeta with ErrorShape restricted to string, which seems impossible without re-defining DefaultMetadata myself. |
Beta Was this translation helpful? Give feedback.
-
How does that work with asynchronous schema validation on the client side? I played around with standard schema yesterday, and noticed that some fields would not should validation errors when I submitted the form empty. When I switched from safeParseAsync to safeParse, it worked again. Does async validation require some kind of suspense around the form elements? |
Beta Was this translation helpful? Give feedback.
-
I am trying to add new value by using intent from useForm, but my ide show me type error - intent.insert({
name: name,
index: index + 1,
defaultValue: "",
});
|
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
-
We are super excited to announce the future
useForm
hook today! This brings first-class support for React 19 Server Actions, a refined validation setup, and more.The motivation
When we first built Conform v1, we had a simple goal: make form state as accessible as possible to developers. We wanted you to just write
fields.email.value
and have everything work magically.So we synced all form values into React state and built complex subscription patterns to avoid re-renders. It worked beautifully—until we realized we had created something that was incredibly hard to debug and reason about.
Over the last year, we have been thinking about how to simplify this, and then came the lightbulb moment: Conform already uses the DOM as the source of truth. Why are we duplicating all this state in React? What if we just... didn't?
The new useForm hook is the result of that realization. No form values in React state. The rest of the state like errors or touched fields doesn't change that often, so we can model it with a simple
useState
. This makes the architecture much simpler and easier to maintain.What's different
This release has addressed a lot of the outstanding issues with new features and improvements. Here are some of the highlights:
Metadata
The most obvious change is that the new hook returns a structured object instead of an array tuple, with some metadata changes:
form.props
directly instead of usinggetFormProps(form)
form.validate()
andform.reset()
are now available on theintent
object insteadform.dirty
) is removed. Use useFormData() with the isDirty helper if you need this.getField(name)
,getFieldset(name)
andgetFieldList(name)
.fields.email.value
) are removed. Use useFormData() to read form values when needed.fields.email.initialValue
) are removed. Usefields.email.defaultValue
/fields.email.defaultOptions
/fields.email.defaultChecked
instead.We have also adjusted the return type of useField(), which returns a single field's metadata instead of an array tuple with both form and field metadata:
The arguments of useFormMetadata() have also changed to accept an object with optional
formId
instead of aformId
string directly.Validation
Another big change is the validation system. The
onValidate
callback now receives the parsedpayload
alongside theFormData
and requires you to return a structured error:Of course, we have helpers to integrate with popular schema libraries like Zod:
But we have something even better: standard schema support. Simply pass your schema to
useForm
and it will handle parsing and validation for you:On the server side, you will find a new parseSubmission utility that parses FormData into a structured submission object with a clean payload for easy validation. Use the report helper to send results back to the client:
We have also improved client async validation support. You can now define async validation directly in your schema and pass it to useForm:
However, validation might trigger regardless of which field the user is typing in, so we recommend using a memoize utility to cache async validation results:
You can also validate manually in the
onValidate
callback if you need more control. See the examples section below for more details.Progressive enhancement
The new design takes a more targeted approach to progressive enhancement. You still get core support like preserving user input on validation errors, but some advanced features require additional setup:
parseSubmission
doesn't handle intents automaticallygetButtonProps()
method is no longer availableWe still believe the best way to build resilient web apps is to use the web platform extensively. However, building a form with a complete no-JS experience introduces additional complexity that not every application needs. The new design still supports full progressive enhancement, but it requires additional setup that we will document separately.
Improved intent handling
Previously, calling multiple form actions required wrapping each in
flushSync
to avoid form state sync issues. The newintent
system eliminates this need:You can also access the intent object anywhere in your component tree using the useIntent() hook:
Additional improvements
Custom error handling: By default, Conform focuses the first invalid field when validation fails. You can now customize this behavior with the new
onError
option:React 19 compatibility: Previously, Conform required a special workaround to disable React 19's automatic form reset behavior. We have adjusted the state model so it now works seamlessly with Server Actions.
Examples
We have updated several examples to showcase the new
useForm
hook, including a new React SPA example:Try it out and share your feedback! The API is stabilizing, but we are still open to suggestions before the official v2 release.
Beta Was this translation helpful? Give feedback.
All reactions