RFC: Form Context #331
Replies: 9 comments 48 replies
-
<3 this is awesome, could have saved me many hours in my last project 😂 I would also love to see an example of how one input element could update the value of another input onChange using the context, if that's possible. I wonder how hard it would be to just have one single provider across the whole app.
I think the this would help form values be read or updated more easily from anywhere in the app as long as the form has been registered. |
Beta Was this translation helpful? Give feedback.
-
This looks very promising! 👍 Can you share an example how to use a custom input component, but make the For example, we have this schema: const schema = z.object({
email: z.string().email(),
password: z.string(),
remember: z.boolean().optional(),
}); I want this to fail: <CustomInput name="not-in-schema" formId={form.id} /> but this should be valid: <CustomInput name="email" formId={form.id} /> |
Beta Was this translation helpful? Give feedback.
-
Hi there! First of all: Thanks for the amazing work and your time. Seems to be moving into the right direction and those changes proposed might make it possible for us to fully transition to conform! One quick question, tho. The Is this change as intended and if yes, what would be the alternative way to intercept the form submission? |
Beta Was this translation helpful? Give feedback.
-
This looks great <3 |
Beta Was this translation helpful? Give feedback.
-
Why is passing |
Beta Was this translation helpful? Give feedback.
-
I have also explored how context could be used effectively with conform and ended up with this: function Example() {
const { FormContext, FormItem } = useConformForm({
onValidate({ formData }) {
return parse(formData, { schema });
},
});
return (
<FormContext>
<form method="post">
<FormItem name="name">
<FormLabel>Name</FormLabel>
<Input type="text" />
<FormMessage />
</FormItem>
<FormItem name="city">
<FormLabel>City</FormLabel>
<Input type="text" />
<FormMessage />
</FormItem>
<button>Submit</button>
</form>
</FormContext>
);
const FormLabel = ({ ...props }) => {
const { id, name } = useField();
if (!props.children) props.children = name;
return <label htmlFor={id} {...props} />;
};
/* Implementation for Input & FormMessage would also be here */ How this would work
Possible implementationThis was cobbled together in a few hours, please be gentle 😅 Expand codeimport { Slot } from "@radix-ui/react-slot";
const useConformForm = <
Output extends Record<string, any>,
Input extends Record<string, any> = Output
>(
options: FormConfig<Output, Input>
) => {
const [form, fieldset] = useForm<Output, Input>(options);
const FormContext = ({ children }: { children: React.ReactNode }) => (
<ConformContext.Provider value={{ form, fieldset }}>
<Slot {...form.props} children={children} />
</ConformContext.Provider>
);
return {
FormContext,
FormItem: FormItem<keyof Fieldset<Output>>,
fieldset,
form,
} as const;
};
function FormItem<T extends string>({
name,
children,
}: {
name: T;
children: React.ReactNode;
}) {
const field = useField(name);
return (
<FormItemContext.Provider value={{ field }} children={children} />
);
}
const useField = (name?: string) => {
const { fieldset } = React.useContext(ConformContext);
const { field } = React.useContext(FormItemContext);
if (name) return fieldset[name];
if (field) return field;
throw new Error("Need to specify a name or wrap in a FormItem");
}; Here is a full "working" example: https://codesandbox.io/p/sandbox/conform-context-example-1-4hcjq7?file=%2Fsrc%2FApp.tsx The ProblemReturning the |
Beta Was this translation helpful? Give feedback.
-
Has this been released? |
Beta Was this translation helpful? Give feedback.
-
Hello, Thank you for your work and update !!! I have a some questions/problems in my app with Remix/React
submission.error.user = ['This account is already used']; But now i have an error : TS2339: Property error does not exist on type
const data = useActionData<typeof action>();
const [form, fields] = useForm({
lastSubmission: data.submission, <---- TS2353: Object literal may only specify known properties, and lastSubmission does not exist in type
onValidate({ formData }) {
return parseWithZod(formData, { schema });
},
shouldRevalidate: 'onBlur',
}); How can i fix it ? Thank you so much |
Beta Was this translation helpful? Give feedback.
-
@edmundhung, some early feedback on the excellent
|
Beta Was this translation helpful? Give feedback.
-
RFC: Form Context
Status:
Revised
Background
Conform utilizes the
setCustomValidity()
API and theinvalid
event to propagate the errors as a way to progressively enhancing the browser from validation. This has not been changed since the very first version: It keeps the validation result in the DOM and have each react hook watching their corresponding error state without relying on react context. (i.e. a data store).It sounds great, but...
setCustomValidity()
API. If you have several errors, we need to concatenate them together. To support a more complex error structure, we might need to serialize it as well.<input type="hidden" />
). Disabled or read-only inputs also suffer the same problem.address
as to errors ofaddress.city
)To get around these issues, Conform has to do some hacks, includes appending hidden buttons to your form and rendering your form twice. As it's hard to justify whether it worths relying on the browser APIs in the future, I have been experimenting a different approach based on store subscription with several changes in the APIs.
API Changes
The minimum react version required is now React 18.
Existing APIs:
useForm
,useFieldset
anduseFieldList
The
useForm()
hook is mostly the same:Both
useFieldset()
anduseFieldList()
hooks no longer exists as you can access the inner fieldset or fieldlist directly.You can still pass the metadata down to another component if you want:
But there is now a better way to do it using a form context.
New APIs:
FormProvider
,useField
anduseFormMetadata
By setting up a react context with the
<FormProvider />
, we will now be able to subscribe to the form metadata using theuseField()
hook. This not only avoids prop drilling but also prevent unneccessary re-renders by tracking the usage of indivudal metadata through a proxy and only rerender it if the relevant metadata is changed.The
<FormProvider />
can also be nesteded with different form context and Conform will look up the closest form context unless aformId
is provided.If you want to create a custom input component, it is now possible too!
Similarly, you can access the form metadata on any component using the
useFormMetadata()
hook:FormMetadata & FieldMetadata
The form and field metadata now provide a wide range of information. In addition to the following properties that you might familiar with:
id
: An unique id for the field that could be used with labelerrorId
: An unique id for the error messagedescriptionId
: An unique id for the description elementinitialValue
: The initial value of the fielderrors
: An array of errorskey
: The key of the fieldIt now includes all these properties as well:
value
: The value of the field based on theFormData
APIvalid
: A shorthand forerrors.length === 0
dirty
: A boolean by comparing the current value with the default valueallErrors
: An object including all errors within the fieldset, e.g. { 'address': [], 'address.city': [], 'address.street': [] }allValid
: Similar to valid but checks all errors within the fieldsetBeta Was this translation helpful? Give feedback.
All reactions