I want to create a MyComponent with an "as
" property which allows it to inherit the properties of the as
component, such that I can get TypeScript typings.
e.g.
Usage with HTML elements:
<MyComponent
as="label"
htmlFor="caption" /* this should autocomplete from label */
asdf="" /* this should give error */
className="test"
>
Test
</MyComponent>
With React components:
<MyComponent
as={MyGrid}
rows={4} /* this should autocomplete from MyGrid */
asdf="" /* this should give error */
>
Test
</MyComponent>
I have this so far which works as a component, but unfortunately it doesn't inherit the types.
import { useRef, forwardRef } from "react";
import type {
PropsWithChildren,
ElementType,
ComponentProps,
ForwardedRef,
ReactElement,
} from "react";
type MyProps<T extends ElementType> =
ComponentProps<T> &
PropsWithChildren &
{ as?: T };
function _MyComponent<T extends ElementType = "div">(
{ as, children, ...props }: MyProps<T>,
ref: ForwardedRef<HTMLElement>,
): ReactElement<T> {
const Comp = as ?? "div";
return (
<Comp {...props} ref={ref}>
{children}
</Comp>
);
}
const MyComponent = forwardRef(_MyComponent);
export default MyComponent;
How do I need to change the Typescript so that I can get type suggestions?
I got it working by adding type assertion to the forwardRef call:
export const MyComponent = forwardRef(_MyComponent) as <
T extends ElementType = "div"
>(
props: MyProps<T> & { ref?: React.Ref<Element> }
) => ReactElement | null;
<MyComponent
as="label"
htmlFor="caption" // this autocompletes
asdf="" // this gives error
>
Test
</MyComponent>
const MyGrid = ({ rows }: { rows: number }) => (
<div style={{ gridTemplateRows: `repeat(${rows}, 1fr)` }}></div>
);
<MyComponent as={MyGrid} rows={4}>Test</MyComponent>;
Modifying MyProps
so it works for HTML elements
type MyProps<T extends ElementType> = {
as?: T;
} & ComponentPropsWithoutRef<T>;