-
Notifications
You must be signed in to change notification settings - Fork 883
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
RFC: Form
primitive
#1977
RFC: Form
primitive
#1977
Conversation
This is great 👏 |
I think this is great and definitely something I would use. I'm trying to move away from state based forms and into native forms but that does come with its own set of challenges. I have one question regarding the error type and messages. Perhaps instead of a component for each desired error type you could provide Since |
Thanks for the feedback @Murkrage!
That's interesting. Although a potential issue is that it moves us away from the 1:1 mapping between part and elements. As there may be situations where multiple messages are visible at once, you'd generally want access to the element for styling for example.
Yeah that's included in the RFC already in the styling section: https://github.com/radix-ui/primitives/blob/form-rfc/rfcs/2023-radix-form-primitive.md#styling |
That's not something I had considered, to be honest. I think there's merit for both implementations and ultimately it's subjective whether someone "likes" the extra components for messaging or not. I suppose someone could always create a wrapping component around all of the messages to achieve the same result. |
rfcs/2023-radix-form-primitive.md
Outdated
<Form.Field name="name"> | ||
<Form.Label>Full name</Form.Label> | ||
<Form.Control /> | ||
<Form.ClientMessage type="customError" isValid={(value, fields) => value === 'John'}> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
isValid
seems like a boolean prop at first glance, instead of a function. How about validator
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's a good point, we can check prior art too. react-hook-forms
seem to use validate
for custom validation, so perhaps we can go with this too, it definitly is better as a verb I agree.
If using this, how it would integrate with React Hook Form or similar libraries? Or is the idea that you wouldn't need those anymore? |
Great work on the RFC!
Yes! Combining the browser constraint validation API makes the logic part intuitive, while the Radix pattern of splitting a component into multiple parts + the asChild approach makes composability simple and advanced things like dynamically generated fields frictionless. As with every other primitive, the data- attributes provide an easy way to style different states, and I especially like the control given by ClientMessage being a component for each error type. For this bit how would someone achieve "I want to add a comma between each of my errors", would checking the The server Errors made sense and having them based on the name of the Form.Field is nice, but I'd having something like Also, the isValid prop is already very useful, but are there any plans to integrate with validation libraries that provide a schema like ZOD? |
This is exciting! I'm interested in how this would integrate with the React Router |
@ttsirkia Yes that's the idea, it should tackle yours needs around form validation so it wouldn't make much sense to use both. Are there things you can think of that wouldn't be possible with our API but would be with React Hook Form or similar libs? |
Thanks for the feedback @bdsqqq!
The use-cases for multiple messages showing at once are limited but could potentially happen so this is a good question. At the moment each .wrapper .message:not(:last-child):after {
content: ", ";
}
Having a more general access anywhere inside
I haven't played much with ZOD, if it gives you a function back to run validation then you should be able to use it in |
@jamesopstad I am not familiar with React Router They specifically mention:
Which is exactly what ours is. I think as long as their import * as Form from '@radix-ui/react-form';
import { Form as RouterForm } from "react-router-dom";
<Form.Root asChild>
<RouterForm>
…
</RouterForm>
</Form.Root> One to play around with to check compatibility but I think it's promising. |
@benoitgrelard This sounds very promising!
I've just had a quick look at the source code and all of the above are true. The nice thing about the |
I could see the benefits of using Radix to provide the accessibility part and React Hook Form the state management and validation (maybe with Zod) etc. |
@ttsirkia, as far as I understand, React Hook Form doesn't deal with any accessibility (they just have a quick section about it, with not great advice). So I see where you're coming from. That being said it would be quite difficult to dissociate the 2 because they are integrally linked, ie. the accessibility is based off of the native validation too. |
@jamesopstad, that's awesome. I'll play around with it but yeah sounds like things should slot in nicely 🙂 And yes that is the nice thing about |
Did you consider supporting schema-based validation with a library like Zod? |
okay, i am so fricken' excited that this is finally happening 🎉 yay! a few bits i would love to see that i didn't see mentioned (or may have missed, 'pologies). it was common in ye olden' days to be able to name fields like <Form.Field name="pets[][breed]" >
<Form.Control type="checkbox" value="dog" />
<Form.Control type="checkbox" value="cat" />
</Form.Field> would output: {
pets: [
{ breed: 'dog' },
{ breed: 'cat' }
]
} here's a version of my own attempts at this declarative Form idea from many moons ago (so ignore how scrappy it is) that also uses named form groups (rendered as another thing perhaps worth pointing out from the above example is that validation is not restricted to being rendered alongside the field itself because it associates itself using a also, i agree with the |
It would be nice to have |
@jamiebuilds i presume Radix will do this internally already for you because that is needed to associate |
|
This makes a lot of sense. I'll raise this as another pro to exposing each message as a component instead of a single object or something similar.
The biggest reasons why someone would use ZOD are to share a validation schema between the back and front end and to be able to infer the type of the form data. I think you could validate each field individually using zod, but the big win would come if you were able to pass the whole schema to the form as a validator. as an example, react-hook-form would do: import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import * as z from 'zod';
const schema = z.object({
name: z.string().min(1, { message: 'Required' }),
age: z.number().min(10),
});
const App = () => {
const {
register,
handleSubmit,
formState: { errors },
} = useForm({
resolver: zodResolver(schema),
});
return (
<form onSubmit={handleSubmit((d) => console.log(d))}>
<input {...register('name')} />
{errors.name?.message && <p>{errors.name?.message}</p>}
<input type="number" {...register('age', { valueAsNumber: true })} />
{errors.age?.message && <p>{errors.age?.message}</p>}
<input type="submit" />
</form>
);
}; I see why this might not belong in the primitive itself, but I think it would be useful to expose a way for the consumer(or more realistically, library authors) to define custom resolvers that apply to the whole form instead of single fields. Additionally, this is probably outside of 99% of use cases, but would come up if custom resolvers were a thing. eg: <Form.Field name="something">
<Form.Control type="number"/>
<Form.ClientMessage type="rangeUnderflow" isValid={(value, fields) => value <= 10}>
Too big.
</Form.ClientMessage>
</Form.Field> instead of <Form.Field name="something">
<Form.Control type="number" max="10"/>
<Form.ClientMessage type="rangeUnderflow">
Too big.
</Form.ClientMessage>
</Form.Field> Again, amazing work on the RFC, excited to see it develop! |
Here's the production link now: https://www.radix-ui.com/docs/primitives/components/form |
Really excited about this! 🤯 My two cents: Not having it would mean some people coming up with their own implementation, whic adds a lot of maintenance to users and would sometimes be a reason to use another lib that supports it (personal experience) |
Hey @franciscohanna92 yeah that is something we have been thinking about. We are likely to include an API to handle such cases. That's also one reason for pushing this out in preview, so people can use it in all these kinds of scenarios and so we can see what emerges from it as needs. |
Great work on this guys! Really liked it! Have you thought about supporting multiple validation strategies? I think that currently it is only possible to validate when submit? it would be nice to have something like: <Form.Field validationStrategy="onSubmit | onBlur | onChange"
...
</Form.Field> as the Constraint validation API is used this can be done on the user-land with |
Yes definitely something on the list, or at least something we thought people would ask for having more control over. For now we went with what we believe is a good default UX-wise but I think there's definitely room for that in the future. |
Does anyone have an example of how this would work with react-select? An example would be (1) requires a selection and (2) requires a number greater than 5. |
This appears to already be implemented: https://www.radix-ui.com/docs/primitives/components/form Is there a reason this hasn't been merged? |
We kept it open for now as the main place for feedback since the component is in "preview" for now. |
I really like how simple and composable the One thing that does not work yet is checking if a form is valid without triggering all validation messages. Natively there are |
We are building on top of the native functionality, so you should still be able to call |
@benoitgrelard yes, calling Edit: Here's a codepen showing the difference with native behavior: |
I understand the difference, I just assumed it would work the same given we just build on top.
So, because we hook onto |
@benoitgrelard ok, seems hard to make the native way work while using |
Yeah will need to look into it, the way the API call fire events seems counter-intuitive, so not sure if I'm missing something or what. What is the use-case btw? |
@benoitgrelard my current use case: on a page with multiple forms i want to show a visual status indicator to the user once all fields on a form are filled and valid. i tried checking the validation state of each form with a debounced |
@jjenzz the link does not work for me |
@slavanga sorry, it seems codesandbox makes sandboxes private by default these days. the link should work now 🙏 |
@jjenzz thank you for putting this together. |
agreed, an |
Have been using the Form component for a while now, and it's been working fine. There are two very critical points, which I hope will be fixed before the release:
@benoitgrelard Are there any plans/estimates for these two issues? |
@snelsi, definitely planning to work on it yet. No estimates at the moment I'm afraid. |
@benoitgrelard I noticed that error messages are linked to the |
Sadly, support for that aria attribute is lacking, so it's best to use describedby |
Radix.Select need to be able to get an id attribute from Form.Control, or at least be able to set an id attribute to Radix.Select |
This patch adds a new progressively enhanced login form using `shadcn` components and my own very simple style wrapper around the Radix `<Form>` primitive. I'm using `conform-to` for server-side and client-side error surfacing with `zod` for schema validation. This login form is progressively enhanced, meaning that form validation works the same both with and without JS enabled client-side. If JS is enabled client-side, that just improves performance by checking form validation client-side before running those same checks server-side. See: radix-ui/primitives#1977 (comment) See: https://github.com/rphlmr/remix-radix-form/blob/main/app/routes/with-conform.tsx See: https://conform.guide/ See: https://www.radix-ui.com/docs/primitives/components/form#server-side-validation See: https://ui.shadcn.com/docs/components/input#with-label Closes: NC-630
This patch adds a new progressively enhanced login form using `shadcn` components and my own very simple style wrapper around the Radix `<Form>` primitive. I'm using `conform-to` for server-side and client-side error surfacing with `zod` for schema validation. This login form is progressively enhanced, meaning that form validation works the same both with and without JS enabled client-side. If JS is enabled client-side, that just improves performance by checking form validation client-side before running those same checks server-side. See: radix-ui/primitives#1977 (comment) See: https://github.com/rphlmr/remix-radix-form/blob/main/app/routes/with-conform.tsx See: https://conform.guide/ See: https://www.radix-ui.com/docs/primitives/components/form#server-side-validation See: https://ui.shadcn.com/docs/components/input#with-label Closes: NC-630
This RFC proposes adding a new primitive to Radix UI primitives:
Form
. It will provide an easier way to create forms in React. The goal is to provide a simple, declarative, uncontrolled (but controllable) API (à la Radix) to create forms that includes client-side validation in an accessible manner, as well as handling of server errors accessibly too.View rendered RFC
We would love your input on this 🙏