radix-ui / primitives

Radix Primitives is an open-source UI component library for building high-quality, accessible design systems and web apps. Maintained by @workos.
https://radix-ui.com/primitives
MIT License
15.91k stars 832 forks source link

Hoist Form ValidityState #2714

Open seanconnollydev opened 9 months ago

seanconnollydev commented 9 months ago

Feature request

Overview

The ValidityState component is handy if you need to access a form field's validity state inline but there are a few scenarios where it falls short:

Examples in other libraries

react-hook-form allows you to access form state outside of the form itself.

import { useForm } from 'react-hook-form';

function App() {
  const {
    register,
    handleSubmit,
    formState: { errors },
  } = useForm();

  return (
    <form onSubmit={handleSubmit((data) => console.log(data))}>
      <input {...register('firstName')} />
      <input {...register('lastName', { required: true })} />
      {errors.lastName && <p>Last name is required.</p>}
      <input {...register('age', { pattern: /\d+/ })} />
      {errors.age && <p>Please enter number for age.</p>}
      <input type="submit" />
    </form>
  );
}

Who does this impact? Who is this for?

I think anyone working with forms can benefit from this feature. It's especially helpful in a use case I have where I have multiple forms that need to be filled out in a single flow with components outside the forms themselves that need to be aware of form state.

forms

Additional context

I noticed FormValidityState uses a useValidationContext hook. This hook seems to have what we would need to access form state outside of the form itself but I'm not 100% sure.

https://github.com/radix-ui/primitives/blob/c31c97274ff357aea99afe6c01c1c8c58b6356e0/packages/react/form/src/Form.tsx#L588

benoitgrelard commented 8 months ago

Hey @seanconnollydev, you should be able to access a state outside of a FormField by passing it the matching name prop you are trying to refer to, in other words when nested inside a FormField it automatically wires the name for you.

zmillman commented 6 months ago

I just ran into this issue as well.

I'm building a form with multiple required fields and I'd like to disable the submit button until all the required fields are filled in (ideally with context about which fields need to be filled in, but that's nice to have).

jagretz commented 5 months ago

...you should be able to access a state outside of a FormField by passing it the matching name prop you are trying to refer to...

I'm not sure I understand this correctly. When attempting something similar to disable the submit button, I wrap the Submit in a render prop from From.ValidityState and it throws the error, createContext.tsx:72 Uncaught Error:FormValidityStatemust be used withinFormField``.

This is how the layout looks.

export default () => (
  <Form.Root>
    <Form.Field name="test">
      <Form.Label />
      <Form.Control />
      <Form.Message />
    </Form.Field>

    <Form.ValidityState name="test">
      {(validity) => <Form.Submit />}
    </Form.ValidityState>
  </Form.Root>
);

That's my understanding of the use case at least. Do you have an example we can reference for how this works outside of the <Form.Field>?

anneclairelh commented 4 months ago

...you should be able to access a state outside of a FormField by passing it the matching name prop you are trying to refer to...

I'm not sure I understand this correctly. When attempting something similar to disable the submit button, I wrap the Submit in a render prop from From.ValidityState and it throws the error, createContext.tsx:72 Uncaught Error:FormValidityStatemust be used withinFormField``.

This is how the layout looks.

export default () => (
  <Form.Root>
    <Form.Field name="test">
      <Form.Label />
      <Form.Control />
      <Form.Message />
    </Form.Field>

    <Form.ValidityState name="test">
      {(validity) => <Form.Submit />}
    </Form.ValidityState>
  </Form.Root>
);

That's my understanding of the use case at least. Do you have an example we can reference for how this works outside of the <Form.Field>?

Hi! I'm encountering the exact same issue. Do you have am update on it?

AndrewJSchoen commented 1 week ago

@benoitgrelard it seems like the name passing is broken for both the ValidityState and Message components. As it happens, there seems to be a PR to fix the issue for Messages. Perhaps a similar fix could be applied to ValidityState and merged? #3044