skip to Main Content

I have a React component written in Typescript called GradientText.tsx:

import React, { ReactNode } from "react"
import { cn } from "@/lib/utils"

// Define a mapping of valid HTML tags and their corresponding JSX components
const typeMap = {
    h1: "h1",
    h2: "h2",
    p: "p",
    // Add other valid HTML tags here
}

interface GradientTextProps extends React.HTMLAttributes<HTMLElement> {
    children: ReactNode // Specify ReactNode type for children prop
    className?: string
    type?: keyof JSX.IntrinsicElements
}

function GradientText({
    children,
    className = "",
    type = "h1",
}: GradientTextProps) {
    // Get the corresponding JSX component based on the 'type' prop
    const TagComponent = typeMap[type as keyof typeof typeMap] || "h1"

    return (
        <TagComponent
            className={`leading-tight ${cn(
                "bg-gradient-to-r from-primary via-red-400 to-pink-500 text-transparent bg-clip-text",
                className,
            )}`}
        >
            {children}
        </TagComponent>
    )
}

// Set defaultProps for className
GradientText.defaultProps = {
    className: "",
    type: "h1",
}

export default GradientText

This component is supposed to accept text along with an HTML tag name and color it with a pre-defined gradient. For example:

<GradientText type="p">gradient paragraph</GradientText>
<GradientText type="h1">gradient header</GradientText>

The gradient displays the expected output in the browser. However, in VSCode, it shows two errors:

Type ‘{ children: ReactNode; className: string; }’ is not assignable to type ‘IntrinsicAttributes’.
Property ‘children’ does not exist on type ‘IntrinsicAttributes’.

Again, the text displays alright despite this error.

UPDATE

As suggested in the first answer below, added extends React.HTMLAttributes<HTMLElement> to my interface, but the problems persist. TS still saying:

Type ‘{ children: ReactNode; className: string; }’ is not assignable to type ‘IntrinsicAttributes’.
Property ‘children’ does not exist on type ‘IntrinsicAttributes’.

UPDATE 2:

One way to prevent the error completely without breaking functionality is to just use type as string with multiple ifs to render the desired element:

import React, { ReactNode } from "react"
import { cn } from "@/lib/utils"

interface GradientTextProps extends React.HTMLAttributes<HTMLElement> {
    children: ReactNode // Specify ReactNode type for children prop
    className?: string
    type?: string
}

function GradientText({
    children,
    className = "",
    type = "p",
}: GradientTextProps) {
    const style = `leading-tight ${cn(
        "bg-gradient-to-r from-primary via-red-400 to-pink-500 text-transparent bg-clip-text",
        className,
    )}`
    // Get the corresponding JSX component based on the 'type' prop
    if (type === "h1") return <h1 className={style}>{children}</h1>
    if (type === "h2") return <h2 className={style}>{children}</h2>
    if (type === "h3") return <h3 className={style}>{children}</h3>
    if (type === "h4") return <h4 className={style}>{children}</h4>
    if (type === "h5") return <h5 className={style}>{children}</h5>
    if (type === "h6") return <h6 className={style}>{children}</h6>
    return <p className={style}>{children}</p>
}

// Set defaultProps for className
GradientText.defaultProps = {
    className: "",
    type: "p",
}

export default GradientText

But I was hoping for a way to DRY this algorithm. Instead of having to write half a dozen ifs, I wanted to use a single array-like implementation to dynamically generate the invoked tag.

2

Answers


  1. To have an interface that allows your custom components to take custom props such as type as well as html attribute props or react-specific html props such as using className instead of class, you should ideally extend your interface from

    React.HTMLAttributes<HTMLElement>
    

    This will stop the warning you are getting such that children and className will now be recognized as IntrinsicAttributes. TypeScript needs to know this somehow.

    interface GradientTextProps extends React.HTMLAttributes<HTMLElement> {
        children: ReactNode
        className?: string
        type?: keyof JSX.IntrinsicElements
    }
    Login or Signup to reply.
  2. Your problem is not that simple.
    To reach your goal, we need to use something called Polymorphic components
    Read more: here

    Simple code like that, you have to defined a React Component like this:

    type TextProps<C extends React.ElementType> = {
        as?: C;
      };
      
      type Props<C extends React.ElementType> = React.PropsWithChildren<
        TextProps<C>
      > &
        Omit<React.ComponentPropsWithoutRef<C>, keyof TextProps<C>>;
      
    export const Text = <C extends React.ElementType = 'span'>({
        as,
        children,
        ...restProps
    }: Props<C>) => {
        const Component = as || 'span';
        return (
            <Component {...restProps}>
                {children}
            </Component>
        );
    };
    

    And use this like this:

    <Text as={'p'} className='someClass'/>
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search