2

I'm trying to create an element in React with Typescript based on the tagName passed as props, and I want to add relative element props based on that tagName. Here is my code.

type ElementProps<Tag extends keyof JSX.IntrinsicElements> =
  JSX.IntrinsicElements[Tag];

type Props = {
  tagName?: React.ComponentType | keyof JSX.IntrinsicElements;
};

const Editable: React.FC<Props> = ({
  tagName = 'div',
  ...props
}) => {
  const htmlEl = useRef(null);

  const elementProps: ElementProps<typeof tagName> = {
    ...props,
    ref: htmlEl,
  };

  return createElement(tagName, elementProps, children);
};

I'm getting this error

Type 'keyof IntrinsicElements | ComponentType<{}>' does not satisfy the constraint 'keyof IntrinsicElements'.
  Type 'ComponentClass<{}, any>' is not assignable to type 'keyof IntrinsicElements'.
    Type 'ComponentClass<{}, any>' is not assignable to type '"view"'

1 Answer 1

4

You were close.

ElementProps expects a string - keyof JSX.IntrinsicElements whether typeof tagName might be a ComponentType<{}.

Al you need to do is to move type restriction inside a type util.

import React, { useRef, createElement } from "react";

type ElementProps<Tag> =
  Tag extends keyof JSX.IntrinsicElements
  ? JSX.IntrinsicElements[Tag]
  : never

type Props = {
  tagName?: React.ComponentType | keyof JSX.IntrinsicElements;
};

const Editable: React.FC<Props> = ({
  tagName = 'div',
  ...props
}) => {
  const htmlEl = useRef(null);

  const elementProps: ElementProps<typeof tagName> = {
    ...props,
    ref: htmlEl,
  };

  return createElement(tagName, elementProps);
};

Playground

I was wondering if I could get the only detailed props regarding the tag that passed in the tagname?

Sure, it is possible.

import React, { useRef, createElement } from "react";

type ElementProps<Tag> = Tag extends keyof JSX.IntrinsicElements
  ? JSX.IntrinsicElements[Tag]
  : never;

type Values<T> = T[keyof T];

type ObtainHTMLProps<T extends Values<JSX.IntrinsicElements>> =
  T extends React.DetailedHTMLProps<infer Props, HTMLElement> ? Props : never;

type AllowedProps = Values<{
  [Tag in keyof JSX.IntrinsicElements]: {
    tagName: Tag;
  } & ObtainHTMLProps<JSX.IntrinsicElements[Tag]>;
}>;  

const Editable: React.FC<AllowedProps> = ({ tagName = "div", ...props }) => {
  const htmlEl = useRef(null);

  const elementProps: ElementProps<typeof tagName> = {
    ...props,
    ref: htmlEl,
  };

  return createElement(tagName, elementProps);
};

const canvas = <Editable tagName="canvas" width={20} /> // ok
const anchor = <Editable tagName="a" href={'www.example.com'} /> // ok

const invalid = <Editable tagName="canvas" href={'www.example.com'} /> // expected error

See react types node_modules\@types\react\index.d.ts. You will find there IntrinsicElements

enter image description here

Having tagName you can easily infer first argument of DetailedHTMLProps. See ObtainHTMLProps.

Now, you need to create a union of all allowed attributes and it is a finite number of props. See AllowedProps.

Sign up to request clarification or add additional context in comments.

2 Comments

It works Thanks, I was wondering if I could get the only detailed props regarding the tag that passed in the tagname?
Thanks appreciate it. 👍

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.