seasonedcc / remix-forms

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

Add prop types of custom components #121

Open bvangraafeiland opened 1 year ago

bvangraafeiland commented 1 year ago

When using the field children, you can pass some props to the Label/Input etc. However, the prop types of the components are JSX.IntrinsicElements['input'] for example, so if you have a custom input component that takes a size prop, you end up with a typescript error.

In reality the props are being passed through, so it will work if you ignore the type error, but it would be nice if the prop types could be merged somehow.

danielweinmann commented 1 year ago

I've been thinking a lot about a good solution for that. The size example came up to me yesterday when I created an example of using Remix Forms with Chakra UI.

In the example, I simply omitted the size prop :P But I think we need to have a better way.

The only solution I thought of so far is to add a generic to Form that will define the types of each component, with the default being what we have now. But I'd love to see if anybody else has ideas :)

@diogob @gustavoguichard, what do you think?

bvangraafeiland commented 1 year ago

The render function of the Field component could be made generic, so in this case:

<Form inputComponent={MyInputComponent}>
  <Field name='name'>
    {({ Input }) => (
      <Input />
    )}
  </Field>
</Form>

The Input render prop of Field would have the props of MyInputComponent.

That being said, I personally think it would be a better to expose a useField to use within custom components (as well as useFormState like mentioned in #37). This makes it easier to implement custom fields that will be default in all forms, rather than relying on the renderField prop. This becomes quite verbose if you want to preserve the layout of checkboxes. With a hook, the required asterisk for example could be done somewhat like this:

const MyLabel = (props) => {
  const { required, label } = useField();
  return <label {...props}>{label}{required && '*'}</Label>;
}

Something similar could be done for indicators on input fields.

danielweinmann commented 1 year ago

I love the idea of the hooks, @bvangraafeiland! Can you create separate issues for each hook you'd like to exist? Thank you!

ReptoxX commented 1 year ago

I'm not sure if this might be helpful, but i'm using a sort of "polymorphic" component in my projects (similar to mantine). With that you can use a component like <MenuItem /> and add a as prop to it and pass another element in and get correct types of that element. It even works with custom Elements and gives you every prop of the custom element.

<MenuItem as='input' />
// Gives you types for the input like value, autocomplete or whatever + the props of MenuItem.
type AsProp<C extends React.ElementType> = {
    as?: C;
    asElement?: C;
};

type PropsToOmit<C extends React.ElementType, P> = keyof (AsProp<C> & P);

export type PolymorphicComponentProp<C extends React.ElementType, Props = {}> = React.PropsWithChildren<Props & AsProp<C>> &
    Omit<React.ComponentPropsWithoutRef<C>, PropsToOmit<C, Props>>;

export type PolymorphicRef<C extends React.ElementType> = React.ComponentPropsWithRef<C>['ref'];
export type PolymorphicComponentPropWithRef<C extends React.ElementType, Props = {}> = PolymorphicComponentProp<C, Props> & {
    ref?: PolymorphicRef<C>;
};

Big thanks to this article: https://blog.logrocket.com/build-strongly-typed-polymorphic-components-react-typescript/