seasonedcc / remix-forms

The full-stack form library for Remix and React Router
https://remix-forms.seasoned.cc
MIT License
491 stars 25 forks source link

Overriding fields #226

Open joaomilho opened 12 months ago

joaomilho commented 12 months ago

So remix-forms allows overriding fields in a number of ways, but its API is still crusty if one wants to integrate a different UI library with it. Let's focus on the example of using NextUI: In NextUI the Input component expects the label as prop. In this case remix-forms allows the following overrides:

// overriding inside the form
<Form schema={schema} >
    {({ Field, Errors, Button, register }) => (
      <Field name="email">
        {({ Errors }) => (
            <>
            <Input {...register("email")} label="Email" />
            <Errors />
          </>
        )}
      </Field>
 )}
</Form>

This works fine, but it's tedious when all the fields have to be overridden in all forms. Now, one can also override the input component:

<Form schema={schema} inputComponent={InputComponent} />

const InputComponent = React.forwardRef<HTMLInputElement, JSX.IntrinsicElements["input"]>((props, ref) => {
  return (
      <Input {...props} label={props.???} />
  );
});

Turns out that because remix-forms assumes the label is a different react component, not just a string, the label is not passed down to the custom input component. Needless to say, overriding the Label component is useless, which leaves us with overriding the Field component.

<Form schema={schema} fieldComponent={FieldComponent} />

const FieldComponent = ((props: JSX.IntrinsicElements['div']) => {
  // props.children[0] is a label component
  return (
      <div {...props}>???</div>
  );
});

Now the issue is that the label is a component passed down to the custom field component as children, so accessing its props is a clumsy thing to do.

Maybe I'm missing something, but it would be nice to have a simpler way to override a full component, with plain (no components) props.

danielweinmann commented 12 months ago

Yeah, component libs that do not match 1:1 the HTML tags in their components are trickier to integrate. That said, some people managed to do a decent job using renderField + useField + useFormState. Personally, I'd advise using Remix Forms with libs that have a 1:1 correlation with HTML, otherwise it might be too cumbersome.

At least until we devise a more flexible API :) we're very open to ideas! I'll leave this issue open for that reason.

mbrowne commented 7 months ago

I was able to solve this particular case using useField:

const FormInputField = React.forwardRef<HTMLInputElement, InputProps>(
    (props, ref) => {
        const { label } = useField()
        return (
            <Input
                ref={ref}
                {...props}
                label={label}
            />
        )
    }
)
FormInputField.displayName = 'FormInputField'

const EmptyComponent = () => null

export function MyForm() {
    return (
        <Form
            schema={schema}
            labelComponent={EmptyComponent}
            inputComponent={FormInputField}
        >
          <Field name="firstName" label="First name" />

If desired, you can also use useField for custom rendering of error messages by passing errorComponent={EmptyComponent} as well.