Search code examples
reactjstypescriptjsx

Create an interface for a subset of JSX.Elements / ReactElement


I would like to group all my icons, so I created an IconInterface

import { IconProps, CaretProps, CheckboxProps } from "./IconProps";

interface IconInterface {
    (props: IconProps | CaretProps | CheckboxProps): JSX.Element;
}

export default IconInterface;

and applied it to my icons like like:

const BarChart: IconInterface = function ({ id, handleClick, useSpan }: IconProps) {
    return (
        <IconContainer id={id} handleClick={handleClick} useSpan={useSpan}>
            <svg>
              // svg code
            </svg>

        </IconContainer>
    );
};

export default BarChart;

But I was hoping to be able to then have the Button element to only accept icons, so I tried:

interface ButtonProps {
    icon?: ReactElement
    ...
}

and I was hoping to be able to, in the render method, do something like

icon ?? (icon)

But that just errors as IconInterface is not of the appropriate type:

TS2322: Type IconInterface | undefined is not assignable to type
string | number | boolean | ReactElement<any, string | JSXElementConstructor<any>> | Iterable<ReactNode> | ReactPortal | null | undefined

I tried to make IconInterface extend ReactElement, but I was unsuccessful, I don't see how to make those match. Is there any way I could do this? I don't want the Button to accept any ReactElement as the icon prop, I would really like it to only accept icons.


Solution

  • I figured it out on myself some days after I posted the question, I hadn't seen @kca's comment at the time, but it's correct the undefined had to be taken into account.

    interface IconInterface {
        (props: IconProps | CaretProps | CheckboxProps): ReactElement;
    }
    
    export default IconInterface;
    

    I applied it to the icons similarly to the way I had in the question:

    const BarChart: IconInterface = function ({ id, handleClick, useSpan }: IconProps) {
        return (
            <IconContainer id={id} handleClick={handleClick} useSpan={useSpan}>
                <svg>
                  // svg code
                </svg>
    
            </IconContainer>
        );
    };
    
    export default BarChart;
    

    Then I was able to use it on the Button props like

    interface ButtonProps {
        icon?: IconInterface;
    

    and then use it in the Button component method like

    const iconElement = icon !== undefined ? (icon()) : null
      return (
        // other code
        {iconElement}
        // more code 
      );