Specify specific props and accept general HTML props in Typescript React App
Solution 1
We can have a look at how div
props are defined:
interface IntrinsicElements {
div: React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>;
}
If we use React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>
as the base type we will have all properties of div
. Since DetailedHTMLProps
just adds ref
to React.HTMLAttributes<HTMLDivElement>
we can use just this as the base interface to get all div
properties:
interface WrapperProps extends React.HTMLAttributes<HTMLDivElement> {
callback?: Function
}
export class Wrapper extends React.Component<WrapperProps>{
render() {
const { callback, children, ...rest } = this.props;
return <div {...rest}>
{children}
</div>;
}
}
export const Test = () => {
return <Wrapper className="test">Hi there</Wrapper> // works now
}
Solution 2
JSX.IntrinsicElements has this info, e.g.
const FooButton: React.FC<JSX.IntrinsicElements['button']> = props => (
<button {...props} className={`foo ${props.className}`} />
)
// alternative...
const FooButton: React.FC<React.PropsWithoutRef<
JSX.IntrinsicElements['button']
>> = props => <button {...props} className={`foo ${props.className}`} />
discovered this in the react-typescript-cheatsheet project.
Solution 3
Have a look at ComponentProps
, ComponentPropsWithRef
, and ComponentPropsWithoutRef
- this will accept a generic input that can be "div"
, "button"
, or any other component. It will include react specific props such as className
as well:
import React, {
forwardRef,
ComponentPropsWithoutRef,
ComponentProps,
ComponentPropsWithRef
} from "react";
const ExampleDivComponent = forwardRef<
HTMLDivElement,
ComponentPropsWithoutRef<"div">
>(({ children, ...props }, ref) => {
return (
<div {...props} ref={ref}>
{children}
</div>
);
});
<ExampleDivComponent
className=""
style={{ background: "green" }}
tabIndex={0}
onTouchStart={() => alert("touched")}
/>;
const ExampleButtonComponent: React.FC<ComponentProps<"button">> = ({
children,
...props
}) => {
return <button {...props}>{children}</button>;
};
<ExampleButtonComponent onClick={() => alert("clicked")} />;
Solution 4
A co-worker of mine figured it out. Sharing here for broader visibility:
interface ComponentPropTypes = {
elementName?: keyof JSX.IntrinsicElements; // list of all native DOM components
...
}
// Function component
function Component({
elementName: Component = 'div',
...rest,
// React.HTMLAttributes<HTMLOrSVGElement>) provides all possible native DOM attributes
}: ComponentPropTypes & React.HTMLAttributes<HTMLOrSVGElement>)): JSX.Element {
return <Component {...rest} />;
}
// Class component
class Component extends React.Component<ComponentPropTypes & React.HTMLAttributes<HTMLOrSVGElement>> {
render() {
const {
elementName: Component,
...rest,
} = this.props;
return <Component {...rest} />
}
}
Related videos on Youtube
Sergej Herbert
Updated on October 29, 2020Comments
-
Sergej Herbert over 3 years
I have a React Wrapper Component, that accepts some props, but forwards all others to the child component (especially relevent for native props like className, id, etc.).
Typescript complains, however, when I pass native props. See error message:
TS2339: Property 'className' does not exist on type 'IntrinsicAttributes & IntrinsicClassAttributes< Wrapper > & Readonly< { children?: ReactNode; }> & Readonly< WrapperProps>'.
How can I get a component with specific props that also accepts native props (without accepting any props and giving up on type checking)?
My code looks like this:
interface WrapperProps extends JSX.IntrinsicAttributes { callback?: Function } export class Wrapper extends React.Component<WrapperProps>{ render() { const { callback, children, ...rest } = this.props; return <div {...rest}> {children} </div>; } } export const Test = () => { return <Wrapper className="test">Hi there</Wrapper> }
FYI: I found a similar question here, but the answer basically gives up type checking, which I want to avoid: Link to SO-Question
-
Sergej Herbert almost 6 yearsThank you very much! This seems a bit verbose. Is there no other "more react" or "more typescript" way to do that? It seems like a pretty usual use-case and extending the HTMLBaseAttributes seems a bit overkill. But the answer solves the question, so thanks!
-
Titian Cernicova-Dragomir almost 6 years@SergejHerbert I meant to remove the
DetailedHTMLProps
part, removed it now. This is the shortest most typescript way to do it as far as I know :) -
Titian Cernicova-Dragomir almost 6 years@SergejHerbert An alternative would be to use an intersection type for the props :
class Wrapper extends React.Component<WrapperProps & React.HTMLAttributes<HTMLDivElement>>
-
webbower over 4 yearsHow would you do accomplish this if the native element rendered by your component is determined via a prop:
function MyComponent({ elementName: Component, children, ...props }) { return (<Component {...props}>{children}</Component>); }
since a type param is required? -
Brunno Vodola Martins over 4 years@webbower I've had a similar issue and for a generic HTML element, I've used:
interface WrapperProps extends React.HTMLAttributes<HTMLElement>