FacundoGFlores
FacundoGFlores

Reputation: 8128

Typing in proper way objects as props

Currently I have the following component

SearchButton.tsx

export interface Props {
  // some other props
  onClick?: (event: React.MouseEvent<HTMLButtonElement>) => void;
}

export const SearchButton: React.SFC<Props> = ({
  // other props
  onClick,
})

Popover.tsx

interface ButtonComponentProps {
  onClick: (event: React.MouseEvent<HTMLButtonElement>) => void;
}

interface Props {
  buttonComponent: React.ComponentType<ButtonComponentProps>;
}

class FilterPopover extends React.Component<Props, State> {
    render() {
        const {buttonComponent: BtnComponent} = this.props;
        return (
            <div>
                //...
                <BtnComponent onClick={this.open} />
            </div>
        )
    }

}

Page.tsx

<Popover buttonComponent={SearchButton}/>

But I have a mistmatch between types:

The expected type comes from property 'buttonComponent' which is declared here on type 'IntrinsicAttributes & IntrinsicClassAttributes<Popover> & Readonly<{ children?: ReactNode; }> & Readonly<Props>'

So if I change the props from Popover.tsx

interface ButtonComponentProps {
  onClick?: (event: React.MouseEvent<HTMLButtonElement>) => void;
}

then interfaces match, but how Typescript is inferring this type through SearchButton? Is it ok to match interfaces in that way? Why those ones should be the same?

React 16.4.1 Typescript 3.0.1

Upvotes: 2

Views: 10502

Answers (1)

Matt McCutchen
Matt McCutchen

Reputation: 30999

TypeScript is comparing the type of SearchButton, which is React.SFC</*SearchButton*/ Props>, to the type of the buttonComponent prop of FilterPopover, which is React.ComponentType<ButtonComponentProps>. Looking at the definition of React.ComponentType<P>, one alternative is React.StatelessComponent<P>, which is another name for React.SFC<P>. React.SFC<P> is invariant in P, meaning that the types substituted for P (SearchButton Props and ButtonComponentProps) must match exactly.

You might think a component that accepts an optional prop onClick should be usable in place of a component that accepts a required prop, because it just means that the optional property will always be set. However, this isn't the case because of the way the optional propTypes and defaultProps fields of a stateless component have been declared. I'm not familiar with the detailed rationale behind the design of the type declarations. The easiest thing would be to just match your props interfaces exactly.

Upvotes: 1

Related Questions