Search code examples
javascriptreactjstypescriptreact-props

Specify specific props and accept general HTML props in Typescript React App


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


Solution

  • 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
    }